More succinct way to use shims in variadic templates?

C++ templates are generally assimilated to creators of bloat, and the Shim idea deals with exactly that: making the template just a thin wrapper over a regular function. It's a really great way to cut down on the bloat.

For example, let's use a simple shim:

//
// Shim interface
//
struct Interface {
    virtual void print(std::ostream& out) const = 0;
}; // struct Interface

std::ostream& operator<<(std::ostream& out, Interface const& i) {
    i.print(out);
    return out;
}

template <typename T>
struct IT: public Interface {
    IT(T const& t): _t(t) {}
    virtual void print(std::ostream& out) const { out << _t; }
    T const& _t;
};

template <typename T>
IT<T> shim(T const& t) { return IT<T>(t); }

Now, I can use it like so:

void print_impl(Interface const& t);

template <typename T>
void print(T const& t) { print_impl(shim(t)); }

And no matter how print_impl is implemented, print remains very lightweight and should be inlined. Easy peasy.


C++11 however introduces variadic templates. The typical urge then is to reimplement all unsafe C-variadics with C++11 variadic templates, even Wikipedia suggests so with a printf implementation.

Unfortunately, Wikipedia's implementation does not deal with positional arguments: the kind that allows you to specify print the 3rd parameter there, etc... It would be easy, if only we had a function with this prototype:

void printf_impl(char const* format, Interface const* array, size_t size);

or similar.

Now, how do we bridge from the original interface :

template <typename... T>
void printf(char const* format, T const&... t);

to the signature above ?

One difficulty with the shims is that they rely on the binding to const-ref behavior to extend the lifetime of the temporary wrapper created just enough without having to allocate memory dynamically (they would not be cheap if they did).

It seems difficult though to get that binding + the array transformation in one step. Especially because arrays of references (and pointer to references) are not allowed in the language.


I have a beginning of a solution, for those interested:

//
// printf (or it could be!)
//
void printf_impl(char const*, Interface const** array, size_t size) {
    for (size_t i = 0; i != size; ++i) { std::cout << *(array[i]); }
    std::cout << "n";
}

template <typename... T>
void printf_bridge(char const* format, T const&... t) {
    Interface const* array[sizeof...(t)] = { (&t)... };
    printf_impl(format, array, sizeof...(t));
}

template <typename... T>
void printf(char const* format, T const&... t) {
    printf_bridge(format, ((Interface const&)shim(t))...);
}

however you will note the introduction of a supplementary step, which is a bit annoying. Still, it appears to work.

I would be very grateful if someone had a better implementation to propose.


@Potatoswatter suggested using initializer lists, which helps a bit (no range-for there).

void printf_impl(char const*, std::initializer_list<Interface const*> array) {
    for (Interface const* e: list) { std::cout << *e; }
    std::cout << "n";
}

template <typename... T>
void printf_bridge(char const* format, T const&... t) {
    printf_impl(format, {(&t)...});
}

But still does not solve the intermediate function issue.


Making it lightweight hinges on eliminating the type parameterization. Your shim potentially instantiates something heavy-duty with the expression out << _t , so it might not really be a good example.

C varargs handles the problem by implicitly casting everything to intptr_t . If you only want to replicate C printf functionality, you can do the same with reinterpret_cast and an initializer_list .

template <typename... T>
void printf(char const* format, T const&... t) {
    printf_impl(format, { reinterpret_cast< std::intptr_t >( t ) ... } );
}

This is obviously suboptimal, but shims are inherently limited. You could do something else with polymorphic types in the initializer_list if you wanted.

In any case, this is exactly what initializer_list is meant for. It can only be constructed from a braced-init-list, making its size a compile-time constant. But the size can only be read back as a runtime constant. So its only practical use is to funnel templates differing only in list length to a common, variable-length implementation.

Add to that the lifetime semantics of initializer_list arguments — the objects are created in a contiguous array on the stack and die when the function call statement ends — and initializer_list looks a lot like <varargs> ! (Edit: or your solution, which I have now actually gone back and read :vP )

Edit: Since containers can't directly store polymorphic objects, and smart pointers aren't appropriate for temporary argument objects, implementing polymorphism would require taking pointers to temporaries. Ugly, but legal due to the lifetime guaranteed for temporary objects:

template <typename... T>
void printf(char const* format, T const&... t) {
    printf_impl(format, std::initializer_list< Interface const * >
        { & static_cast< Interface const & >( shim(t) )... } );
}

If you can use homogenous (same size and alignment in memory) types take a look at that:

// thin template layer over regular class/methods
template< typename T, typename... Contracts>
inline void Container::bindSingleAs(){

isMultiBase< T, Contracts...>(); //compile time test

    priv::TypeInfoP         types[ sizeof...( Contracts)]
                            { &typeid( Contracts)... };

    priv::SharedUpcastSignature upcasts[ sizeof...( Contracts)]
                            { &priv::shared_upcast< T, Contracts>... };

    // dispatch over non-template method.
    container->bindSingleAs( &typeid(T), types, upcasts, sizeof...(   Contracts));
}

Now after editing due to comments, I think there are 2 conflicting requisites.

  • Want an array parameter
  • Want no copy overhead
  • If the printf_impl function require an array as parameter, then this mean that the array elements should have the same disposition in memory (that means that if 1 element is 64 bytes that forces all other elements to be 64 bytes aligned even if they are 1 byte..) hence a copy is necessary , or at least the copy to a pointer to a fixed location, so it is definitely NOT POSSIBLE do what OP wanted.

    We can still build that array but we are constrained :

  • We Don't want copying at all, then we should statically declare the type of the array, this force us to build a third type.

    auto Array = MakeArray( /* values*/);

    printf( Array);

  • We accept copying, so we build the array inside the function, since values are not known we can hide array from user but we have to copy the parameters to fixed memory locations, however we still have the array hided under the hood.

  • Heap allocation, that allows to pass parameters in a very compact array, however the parameters have to reside elsewhere and heap allocation may be costly.

  • The first solution is to accept an extra complexity in coding by creating a statically typed array wich elements can be addressed (all aligned to biggest element), however that is suboptimal since that increase the size of the object wich can hit anyway performance (if that array lives even after the function call)

    The second solution hides the complexity behind the template interface, however it cannot avoid the performance cost of copying values temporarily to an array identical to the one of the first solution.

    So, it is not possible doing so, sorry. The other answer falls between number 2 and 3. All other possible answers would be within one of the 3 categories.

    链接地址: http://www.djcxy.com/p/10656.html

    上一篇: 如何保存在webview中显示的图像?

    下一篇: 在variadic模板中使用垫片更简洁的方法?