16.4.2. Pack Expansion

Image

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);

Understanding Pack Expansions

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_rep16.3, p. 695) on each of its arguments and then calls print to print the resulting strings:

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


Image Note

The pattern in an expansion applies separately to each element in the pack.



Exercises Section 16.4.2

Exercise 16.56: Write and test a variadic version of errorMsg.

Exercise 16.57: Compare your variadic version of errorMsg to the error_msg function in § 6.2.6 (p. 220). What are the advantages and disadvantages of each approach?


..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.128.145