A more concise way to use gaskets in variable templates?

C ++ templates are usually assimilated to bloat creators, and the idea of ​​Shim deals precisely with what makes the template just a thin shell over a regular function. This is a really great way to reduce swelling.

For example, you can use a simple gasket:

// // 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 this:

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

And no matter how print_impl implemented, print remains very lightweight and needs to be embedded. Easy peasy.


C ++ 11 introduces variable templates. A typical motivation is to reimplement all unsafe C variations with C ++ 11 variable templates, even Wikipedia offers this with a printf implementation .

Unfortunately, the Wikipedia implementation is not related to positional arguments: a type that allows you to specify a third parameter there, etc ... It would be easy if we had a function with this prototype:

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

or similar.

Now , as we modem from the source interface :

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

in the signature above?

One of the difficulties with pads is that they rely on binding to const-ref behavior to extend the life time of a temporary shell created sufficiently without the need to dynamically allocate memory (they would not be cheap if they did).

It seems difficult though to get this binding + array conversion in one step. Moreover, the language does not allow arrays of links (and a pointer to links).


I have a beginning 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 notice the introduction of an extra step, which is a little annoying. However, it works .

I would really appreciate it if someone suggests a better implementation.


@Potatoswatter suggests using initializer lists that help a little (there is no range for them).

 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 problem of intermediate function.

+7
source share
2 answers

Providing a lightweight hinge while eliminating type parameterization. Your pad potentially creates something heavy-duty with the expression out << _t , so this may not be a good example.

C varargs handles the problem by implicitly discarding everything on intptr_t . If you only want to replicate the functionality of C printf , you can do the same with reinterpret_cast and 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 the gaskets are essentially limited. You can do something else with polymorphic types in initializer_list if you want.

In any case, this is exactly what initializer_list . It can only be created from the bit-init-init list, which makes its size a constant compilation time. But size can only be read as a run-time constant. Thus, its only practical application is that the funnel templates differ only in the length of the list to the general implementation with a variable length.

Add to this the semantics of the life of the initializer_list arguments - objects are created on an adjacent array on the stack and die when the function call statement ends, and initializer_list looks just like <varargs> ! (Edit: or your solution, which I have now really returned and read: vP)

Edit: Since containers cannot directly store polymorphic objects, and smart pointers are not suitable for temporary argument objects, to implement polymorphism, you need to bind pointers to temporary ones. Ugly but legal due to the service life 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) )... } ); } 
+3
source

If you can use homogeneous (same size and alignment from memory) look at this :

 // 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 details.

  • Array parameter required
  • No overhead for copying required

If the printf_impl function requires the array parameter as a parameter, this means that the elements of the array must have the same memory location (this means that if 1 element is 64 bytes, which forces all other elements to be aligned by 64 bytes if they are 1 byte .. ) , therefore, you need a copy, or at least a copy of the pointer to a fixed location, so it is definitely NOT ABLE to do what the OP wanted.

We can still build this array, but we are limited :

  • We don’t want to copy at all, then we must statically declare the type of the array, this forces us to build the third type.

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

    printf( Array);

  • We accept copying, so we build the array inside the function, since the values ​​are unknown, we can hide the array from the user, but we have to copy the parameters to the fixed memory cells, however we still have the array hidden under the hood.

  • Heap allocation, which allows you to pass parameters in a very compact array, however the parameters must be located elsewhere, and heap allocation can be expensive.

The first solution is to take on additional coding complexity by creating a statically typed array whose elements can be addressed (all aligned with the largest element), however this is suboptimal as it increases the size of an object that can hit any performance case (if this array works even after a function call)

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

Therefore, this is impossible to do, sorry. Another answer falls between numbers 2 and 3. All other possible answers will be in one of three categories.

0
source

All Articles