What Are the Rules for the "..." Token in the Context of Variadic Templates

What are the rules for the ... token in the context of variadic templates?

In the context of variadic template, the ellipsis ... is used to unpack the template parameter pack if it appears on the right side of an expression (call this expression pattern for a moment), or it's a pack argument if it appears on left side of the name:

...thing  // pack   : appears as template arguments
thing... // unpack : appears when consuming the arguments

The rule is that whatever pattern is on the left side of ... is repeated — the unpacked patterns (call them expressions now) are separated by comma ,.

It can be best understood by some examples. Suppose you have this function template:

template<typename ...T> //pack
void f(T ... args) //pack
{
// here are unpack patterns

g( args... ); //pattern = args
h( x(args)... ); //pattern = x(args)
m( y(args...) ); //pattern = args (as argument to y())
n( z<T>(args)... ); //pattern = z<T>(args)
}

Now if I call this function passing T as {int, char, short}, then each of the function call is expanded as:

g( arg0, arg1, arg2 );           
h( x(arg0), x(arg1), x(arg2) );
m( y(arg0, arg1, arg2) );
n( z<int>(arg0), z<char>(arg1), z<short>(arg2) );

In the code you posted, std::forward follows the fourth pattern illustrated by n() function call.

Note the difference between x(args)... and y(args...) above!


You can use ... to initialize an array also as:

struct data_info
{
boost::any data;
std::size_t type_size;
};

std::vector<data_info> v{{args, sizeof(T)}...}; //pattern = {args, sizeof(T)}

which is expanded to this:

std::vector<data_info> v 
{
{arg0, sizeof(int)},
{arg1, sizeof(char)},
{arg2, sizeof(short)}
};

I just realized a pattern could even include access specifier such as public, as shown in the following example:

template<typename ... Mixins>
struct mixture : public Mixins ... //pattern = public Mixins
{
//code
};

In this example, the pattern is expanded as:

struct mixture__instantiated : public Mixin0, public Mixin1, .. public MixinN  

That is, mixture derives publicly from all the base classes.

Hope that helps.

What is the meaning of ... ... token? i.e. double ellipsis operator on parameter pack

Every instance of that oddity is paired with a case of a regular single ellipsis.

  template<typename _Res, typename... _ArgTypes>
struct _Weak_result_type_impl<_Res(_ArgTypes...)>
{ typedef _Res result_type; };

template<typename _Res, typename... _ArgTypes>
struct _Weak_result_type_impl<_Res(_ArgTypes......)>
{ typedef _Res result_type; };

template<typename _Res, typename... _ArgTypes>
struct _Weak_result_type_impl<_Res(_ArgTypes...) const>
{ typedef _Res result_type; };

template<typename _Res, typename... _ArgTypes>
struct _Weak_result_type_impl<_Res(_ArgTypes......) const>
{ typedef _Res result_type; };

My guess is that the double ellipsis is similar in meaning to _ArgTypes..., ..., i.e. a variadic template expansion followed by a C-style varargs list.

Here's a test supporting that theory… I think we have a new winner for worst pseudo-operator ever.

Edit: This does appear to be conformant. §8.3.5/3 describes one way to form the parameter list as

parameter-declaration-listopt ...opt

So the double-ellipsis is formed by a parameter-declaration-list ending with a parameter pack, followed by another ellipsis.

The comma is purely optional; §8.3.5/4 does say

Where syntactically correct and where “...” is not part of an abstract-declarator, “, ...” is synonymous with “...”.

This is within an abstract-declarator, [edit] but Johannes makes a good point that they are referring to an abstract-declarator within a parameter-declaration. I wonder why they didn't say "part of a parameter-declaration," and why that sentence isn't just an informative note…

Furthermore, va_begin() in <cstdarg> requires a parameter before the varargs list, so the prototype f(...) specifically allowed by C++ is useless. Cross-referencing with C99, it is illegal in plain C. So, this is most bizarre.

Usage note

By request, here is a demonstration of the double ellipsis:

#include <cstdio>
#include <string>

template< typename T >
T const &printf_helper( T const &x )
{ return x; }

char const *printf_helper( std::string const &x )
{ return x.c_str(); }

template< typename ... Req, typename ... Given >
int wrap_printf( int (*fn)( Req... ... ), Given ... args ) {
return fn( printf_helper( args ) ... );
}

int main() {
wrap_printf( &std::printf, "Hello %s\n", std::string( "world!" ) );
wrap_printf( &std::fprintf, stderr, std::string( "Error %d" ), 5 );
}

How to overload variadic templates when they're not the last argument

When a parameter pack doesn't appear last in the parameter declaration, it is a non-deduced context. A non-deduced context means that the template arguments have to be given explicitly. This is why foo #1 is a better overload. You can force the second overload call by providing explicit arguments (foo<int,int>(1,2,3)) or as you said, move the int to the front.

To make things clear, you can overload a function with variadic templates, but when they do not appear as the last argument, they cannot be deduced, which automatically disqualifies them as candidates when explicit arguments are not provided. When they are provided, the template parameters are replaced with their provided types and the resulting non-template function is a candidate in overload resolution.

To answer your question, you can put all arguments into a tuple and pick out the last and test that one. Then pass on an overload based on a simple is_same check:

template<class...Us>
void foo_impl(true_type,Us...); // last argument is int
template<class...Us>
void foo_impl(false_type,Us...); // last argument non-int

template<class...Us>
void foo( Us&&...us ) {
using tuple=tuple<Us...>;
using last=decltype(get<sizeof...(Us)-1>(declval<tuple>()));
foo_impl(is_same<decay_t<last>,int>{}, forward<Us>(us)...);
}

Expansion with variadic templates

Originally I just literally answered the question, but I wanted to expand this somewhat to provide a more thorough explanation of how what packs are expanded into what. This is how I think about things anyway.

Any pack immediately followed by an ellipses is just expanded in place. So A<Ts...> is equivalent to A<T1, T2, ..., TN> and hun(vs...) is similarly equivalent to hun(v1, v2, ..., vn). Where it gets complicated is when rather than a pack followed by ellipses you get something like ((expr)...). This will get expanded into (expr1, expr2, ..., exprN) where expri refers to the original expression with any pack replaced with the ith version of it. So if you had hun((vs+1)...), that becomes hun(v1+1, v2+1, ..., vn+1). Where it gets more fun is that expr can contain more than one pack (as long as they all have the same size!). This is how we implement the standard perfect forwarding model;

foo(std::forward<Args>(args)...)

Here expr contains two packs (Args and args are both packs) and the expansion "iterates" over both:

foo(std::forward<Arg1>(arg1), std::forward<Arg2>(arg2), ..., std::forward<ArgN>(argN));

That reasoning should make it possible to quickly walk through your cases for, say, what happens when you call foo(1, 2, '3').

The first one, gun(A<Ts...>::hun(vs)...); expands Ts "in place" and then there's an expression to expand for the last ellipses, so this calls:

gun(A<int, int, char>::hun(1), 
A<int, int, char>::hun(2),
A<int, int, char>::hun('3'));

The second one, gun(A<Ts...>::hun(vs...)); expands both packs in place:

gun(A<int, int, char>::hun(1, 2, '3'));

The third one, gun(A<Ts>::hun(vs)...), expands both packs at the same time:

gun(A<int>::hun(1), 
A<int>::hun(2),
A<char>::hun('3'));

[update] For completeness, gun(A<Ts>::hun(vs...)...) would call:

gun(A<int>::hun(1, 2, '3'),
A<int>::hun(1, 2, '3'),
A<char>::hun(1, 2, '3'));

Finally, there's one last case to consider where we go overboard on the ellipses:

gun(A<Ts...>::hun(vs...)...);

This will not compile. We expand both Ts and vs "in place", but then we don't have any packs left to expand for the final ellipses.

Array Elements to Variadic Template arguments

You could, with the help of the index sequence trick:

template <class T, size_t N, size_t... Is>
T array_sum_impl(T (&arr)[N], std::index_sequence<Is...>) {
return sum(arr[Is]...);
}

template <class T, size_t N>
T array_sum(T (&arr)[N]) {
return array_sum_impl(arr, std::make_index_sequence<N>{});
}

But it's probably better to just do the normal:

auto sum = std::accumulate(std::begin(array), std::end(array), 0.0);

C++ Wrappers and Variadic Templates Confusion

Class MIDIvec is derived from vector, it's bad design in general and composition will be better.

AddBytes has 3 overloads, one for byte and args pack, one for const char* and args pack and one for empty pack (the end of recursion).

So, let's call it.

AddBytes(byte(1), "abc", byte(2));

Here will be called one with byte first arg, since pack is byte, const char*, byte, then from function will be called one with const char*, since now pack is const char*, byte, then will be called one with byte, since pack is byte and then will be called one without args, since pack is empty.

Handling variadic templates in c++11

What get<1>(t) looks like will depend on the implementation of mtuple. A typical implementation recusively inherits from a type that holds each argument, so mtuple<A,B,C> inherits from TupleHead<A> (which has a member of type A) and also inherits from TupleTail<B,C>. TupleTail<B,C> inherits from TupleHead<B> (which has a member of type B) and TupleTail<C>. TupleTail<C> inherits from TupleHead<C> (which has a member of type C.)

Now, if you give each base class an integer parameter too:

mtuple<A,B,C> inherits from TupleHead<0,A> and TupleTail<1,B,C>

TupleTail<1,B,C> inherits from TupleHead<1,B> and TupleTail<2,C>

TupleTail<2,C> inherits from TupleHead<2,C>

Now it's relatively simple to write get<1>, because the mtuple has a single unique base class of type TupleHead<1,B> which can be obtained by an upcast, then return the B member of that base class.

[Edit: get<1>(m) needs to know the type B that corresponds to the tuple element with index 1, for that you use something like std::tuple_element which also relies on the recursive inheritance hierarchy described above and uses partial specialization to get the TupleHead<1,T> base class with index 1, then determines the parameter T in that partial specialization, which gives B in my example.]

Many of the techniques used with variadic templates are functional programming techniques, such as operating on the first element of the template parameter pack, then recursively doing the same thing on the remainder of the pack, until you've processed all the elements. There aren't many things you can do with a template parameter pack directly except count its size (with sizeof...) or instantiate another template with it, so the usual approach is to instantiate another template that separates the pack Args into ArgHead, ArgsTail... and processes the head, then recursively do the same to ArgsTail

How to form a string using variadic templates without recursion in c++ 11

You don't need to use recursion to make this work.

You can create fake array with expanding parameters pack when filling it:

template<class ... Args>
std::string makeString(Args...args){
std::string s;
int temp[] = { (s += toString(args),0)... }; // concatenation
static_cast<void>(temp);
return s;
}

for toString you provide overloads for handling all types you want.

For example:

template<class T>
std::string toString(T t){
return std::to_string(t); // for numbers
}

std::string toString(const std::string& str){
return str;
}

std::string toString(const char* str){
return str;
}

Full demo


Hmm, you don't even use toString, version with ostringstream is:

template<class ... Args>
std::string makeString(Args&&...args){
std::ostringstream os;
int temp[] = { ((os << std::forward<Args>(args)),0)... };
static_cast<void>(temp);
return os.str();
}

Casting a variadic parameter pack to (void)

When working with variadic template, it is more clean to use sink:

struct sink { template<typename ...Args> sink(Args const & ... ) {} };


#ifdef DEBUG
std::cout << value;
bar(std::forward<Args>(args)...);
#else
sink { value, args ... }; //eat all unused arguments!
#endif


Related Topics



Leave a reply



Submit