Aside from taking its size, the only other thing we can do with a parameter pack is to expand it. When we expand a pack, we also provide a pattern to be used on each expanded element. Expanding a pack separates the pack into its constituent elements, applying the pattern to each element as it does so. We trigger an expansion by putting an ellipsis (. . . ) to the right of the pattern.
For example, our print
function contains two expansions:
template <typename T, typename... Args>
ostream &
print(ostream &os, const T &t, const Args&... rest)// expand Args
{
os << t << ", ";
return print(os, rest...); // expand rest
}
The first expansion expands the template parameter pack and generates the function parameter list for print
. The second expansion appears in the call to print
. That pattern generates the argument list for the call to print
.
The expansion of Args
applies the pattern const Args&
to each element in the template parameter pack Args
. The expansion of this pattern is a comma-separated list of zero or more parameter types, each of which will have the form const
type&
. For example:
print(cout, i, s, 42); // two parameters in the pack
The types of the last two arguments along with the pattern determine the types of the trailing parameters. This call is instantiated as
ostream&
print(ostream&, const int&, const string&, const int&);
The second expansion happens in the (recursive) call to print
. In this case, the pattern is the name of the function parameter pack (i.e., rest
). This pattern expands to a comma-separated list of the elements in the pack. Thus, this call is equivalent to
print(os, s, 42);
The expansion of the function parameter pack in print
just expanded the pack into its constituent parts. More complicated patterns are also possible when we expand a function parameter pack. For example, we might write a second variadic function that calls debug_rep
(§ 16.3, p. 695) on each of its arguments and then calls print
to print the resulting string
s:
// call debug_rep on each argument in the call to print
template <typename... Args>
ostream &errorMsg(ostream &os, const Args&... rest)
{
// print(os, debug_rep(a1), debug_rep(a2), ..., debug_rep(an)
return print(os, debug_rep(rest)...);
}
The call to print
uses the pattern debug_rep(rest)
. That pattern says that we want to call debug_rep
on each element in the function parameter pack rest
. The resulting expanded pack will be a comma-separated list of calls to debug_rep
. That is, a call such as
errorMsg(cerr, fcnName, code.num(), otherData, "other", item);
will execute as if we had written
print(cerr, debug_rep(fcnName), debug_rep(code.num()),
debug_rep(otherData), debug_rep("otherData"),
debug_rep(item));
In contrast, the following pattern would fail to compile:
// passes the pack to debug_rep; print(os, debug_rep(a1, a2, ..., an))
print(os, debug_rep(rest...)); // error: no matching function to call
The problem here is that we expanded rest
in the call to debug_rep
. This call would execute as if we had written
print(cerr, debug_rep(fcnName, code.num(),
otherData, "otherData", item));
In this expansion, we attempted to call debug_rep
with a list of five arguments. There is no version of debug_rep
that matches this call. The debug_rep
function is not variadic and there is no version of debug_rep
that has five parameters.