Document number: P0341R0
Date: 2016-05-30
Reply-To: Mike Spertus, Symantec (mike_spertus@symantec.com)
Audience: SG7 Reflection possibly also Evolution Working Group
Parameter packs have proven exceedingly useful within templates but are unavailable in other contexts where they could prove equally useful. This paper addresses this by extending and generalizing the concept of parameter pack, and more generally of template argument lists, to a much wider range of contexts. Numerous use cases are given.
We will build up our definitions incrementally from consideration of natural use cases.
template<typename ...T>
struct easy_tuple {
template<typename U> easy_tuple(U &&u) : t{u}... {}
T ...t; // Ill-formed. Parameter pack members not allowed
};
The above code appears perfectly natural and leads to a far cleaner and easier to understand implementation of tuple than the usual one. Motivated by this, we propose removing the restriction that only parameter declarations can be packs.
Here is another useful example of non-parameter declarations of packs.
template<typename ...T> T cleanData(T t); // Performs data cleaning: normalizing, removing outliers, etc.
template<typename ...T> void processData(T ...rawData) {
T ...cleanedData{cleanData(raw_data)...};
/* Work with cleanedData */
};
template <int i, typename ...T> auto ith_argument(T && ...t) { /* Usual code to return ith argument }
template<typename int i; typename ...T> auto get(easy_tuple<T...> tup) {
return ith_argument<i>(tup....t& ...); // Access parameter pack from outside its defining template
}
Note: By analogy with how we write a.template b<int>
to indicate that a member is a template, the above code adopt as a bikeshed Richard Smith's suggestion to use ... to indicate
a member of a dependent type is a pack.
template<typename...T = <double, double> > // default to 2-dimensional double-valued Euclidean space
struct euclidean_space { /* ... */ };
auto f() { return {2, "foo"}; // Ill-formed. braced-init-list is not an expression}
The code looks like it is returning an expression, and it seems like the expression should have a type. However, there is special language in the standard for returning a braced-init-list because, although it may often be used like an expression, it is not one. With packs representing their type, braced-init-lists can be first-class expressions, again reducing the number of special cases:
auto f() { return {2, "foo"}; } // Now OK. Return type is <int, char const *>
As in the last section, we can also remove the restriction on packs having default arguments for for function parameters from §8.3.6p3 [dcl.fct.default].
template<typename ...T>
void recognize(T ...ourHeroes = {"Alan Turing", "Dennis Ritchie", "Bjarne Stroustrup"});
Of course, a parameter packs values can also be returned directly.
template<typename ...T>
auto truncate(T ...t) { return min(100, t)...; }
Returning a pack rather than a std::pair or a std::tuple eases
composition and functional programming by increasing loose coupling.
<double, double> calculateTargetCoordinates();
double distanceFromMe(double x, double y);
void launch() {
if(distanceFromMe(calculateTargetCoordinates()...))
getOuttaHere();
}
Note: As mentioned above. Our packs generalize template parameter lists. All packs are types and have instantiable values (provided the types in the pack do). This is straightforward for type parameter packs. E.g., {2, 'c'} is a valid value of the pack type <int, char>. A more interesting example would be the type <string, 7, int> which is a valid template parameter list and therefore represents a pack type. A valid value for this type is {"C++"S, 7, 5}. By encoding known values in the pack type, (possibly significant) storage can be saved in templatized programs.
Pack literals can also name their members, providing a very natural solution to the pack analogue of “named tuples” problem, making it easy to create functions that return multiple values descriptively.
<string topSong, person president, double avgTemp> someFactsAboutYear(int year) {
if(year==1962)
return {"Stranger On The Shore", Presidents.get("Kennedy"), 14};
}
Named packs are implicitly convertible to and from unnamed packs but not each other for added static type safety beyond what is provided in pair and tuple.
Another use case we would like to cover with parameter packs is as typelists.
For this and other reasons, we propose that packs are types and can be used as
types without being expanded. We illustrate the notation for partially specializing
a template on typelists
with an append metafunction for concatenating two typelists.
template<typename L, typename R> struct append;
template<typename ...Ls, typename ...Rs>
struct append< <Ls...>, <Rs...> > {
using type = <Ls..., Rs...>;
};
Note that we are not limited to types and can also do intlists, etc.
template<typename theoretical, typename actual> struct accuracy;
template<double... predictions, double... measurements>
struct accuracy< <predictions...>, <measurements...> >) { /* ... */ };
Using packs as a base typelist type also makes it easier to coexist with the myriad of existing and experimental typelist classes, such as those in Boost MPL, Loki, with more under development. For example, given a pack T = <int, double, long>, we can convert it to a boost::mpl::vector as simply as mpl::vector<T...>
Note that we do not make any claim that packs are the only way to do typelists, but merely that they co-exist well with and between other approaches. See the discussion below about user-defined classes.
We consider tuple as a prototype. For example, we would want tuple<int, double>... to be the pack <int, double> and tuple<int, double>{2, 3.5}... to be the {2, 3.5} value of the pack type <int &, double &>. Here is how these could be implemented in the easy_tuple example above.
template<typename ...T>
struct easy_tuple {
/* ... */
<T&...> operator...() { return t...; }
};
template<typename ...T>
using easy_tuple<T...>... = <T...>;
Notes: