How C++ expands multiple parameters packs simultaneously,

Viewed 75

Having following functions f0, f1, f2 in C++14 code, which accepts arbitrary number of fixed-length arrays:

#include <functional>
template<typename... TS, size_t N> void f0( TS(&& ... args)[N] ) {}
template<typename T, size_t... NS> void f1( T(&& ... args)[NS] )  {}
template<typename... TS, size_t... NS> void f2( TS(&& ... args)[NS] )  {}
 
int main(){
    f0({1,2}, {3.0,4.0}, {true, false});
    f1({1,2,3}, {4,5}, {6});
    f2({1,2,3}, {4.0,5.0}, {true});
    return 0;
}

Function f0 accepts arrays with different types and fixed array length. Function f1 accepts arrays with fixed type and different array lengths. It's clear how this works: C++ compiler deduces variable-length parameter pack in immediate context of template function instantiation, which is expanded in (&& ... args) expression.

Function f2 accepts arrays with different types and different array lengths, which produces two variable-length parameter packs, however there is only one ellipsis operator in pack expansion (&& ... args), but code compiles and works well.

So question is: what is general rule for expanding multiple parameter packs within single ellipsis operator? Obviously, at a minimum, they must be the same length, but what are the other requirements? Is there a precise definition that the n-th element of the first parameter packing should expand along with the n-th element of the second parameter packing?

Also, following code with explicit template argument provision does not compile: f2<int,float,bool,3,2,1>({1,2,3},{4.0f,5.0f},{true});. It would be interesting to know the reasons for this behaviour.

3 Answers

All packs appearing as part of one pack expansion (...) must have exactly the same length. Otherwise substitution fails (which depending on context is a hard error or SFINAE). (see [temp.variadic]/7)

All packs are expanded so that the i-th expanded element of the pack expansion uses the i-th element of each pack. For the detailed expansion rule see [temp.variadic]/8.

(Links are to the post-C++20 draft of the standard, but the same applies to C++14.)

This is specified in C++ Standard section [temp.variadic]. Basically, it's what you described: when a pack expansion expands more than one pack, all those packs must have the same number of elements. And the expansion in most cases forms a list where the nth element in the resulting list uses the nth element of each expanded pack.

More exactly, paragraph 5 defines

A pack expansion consists of a pattern and an ellipsis, the instantiation of which produces zero or more instantiations of the pattern in a list (described below). The form of the pattern depends on the context in which the expansion occurs. Pack expansions can occur in the following contexts:

  • In a function parameter pack; the pattern is the parameter-declaration without the ellipsis.
  • ...

In your example, each function template declares a function parameter pack named args. The patterns are TS(&& args)[N], T(&& args)[NS], and TS(&& args)[NS].

Paragraph 7 (after clarifying which packs are expanded by which pack expansions that when one pack expansion appears inside another) has the requirement

All of the packs expanded by a pack expansion shall have the same number of arguments specified.

And paragraph 8:

The instantiation of a pack expansion that is neither a sizeof... expression nor a fold-expression produces a list of elements E1, E2, ..., EN, where N is the number of elements in the pack expansion parameters. Each Ei is generated by instantiating the pattern and replacing each pack expansion parameter with its ith element.

So yes, for the instantiation of f3 where TS is deduced as int, double, bool and NS is deduced as 3, 2, 1, the pack expansion becomes a function parameter list with types int(&&)[3], double(&&)[2], bool(&&)[1].

A template parameter pack is a template parameter that accepts zero or more template arguments (non-types, types, or templates). A function parameter pack is a function parameter that accepts zero or more function arguments.

A template with at least one parameter pack is called a variadic template.

Visit[1]: https://en.cppreference.com/w/cpp/language/parameter_pack

Visit[2]: Multiple expansions of multiple parameter packs in the same expression

Visit[3]: https://www.ibm.com/docs/en/zos/2.1.0?topic=only-variadic-templates-c11

I hope this help you.

Related