The purpose of this paper is to propose mechanisms and rationale for named parameter packs and literal parameter packs. The main idea is to allow parameter packs to be created and named in non-template contexts by allowing parameter pack literals, which are just a template parameter list enclosed in angle brackets. In particular, this proposal makes parameter packs suitable for use as typelists and allows them to be defined and referenced from outside of template classes. While this is not ready for standardization, I want to give and idea of the main ideas and a number of use cases to get feedback from the Reflection working group in Chicago, so a complete proposal can be provided in Seattle.
We imagine (unconstrained for the moment by reality) a template class
template <typename... Ts> struct parameter_pack {};
We further imagine that a template-parameter-list (§14p1)
surrounded by angle brackets could be used as a “parameter_pack literal.”
<int, std::basic_ostream<char>> // literal for parameter_pack<int, ostream>
We could then create a named parameter
pack by typedef'ing it, like typedef<signed char, short int, int, long int, long long int> signed_integral_types;
Of course, we would also want traditional declarations of parameter packs to also create named parameter packs.
template<typename... Ts> struct S {
static bool is_all_signed_integral_types() { return is_same<Ts, signed_integral_types>::value; }
};
S<double, float>::is_all_signed_integral_types(); // returns false
S<signed char, short int, int, long int, long long int>::is_all_signed_integral_types(); // returns true
Of course, you would be able to instantiate parameter packs as usual:
void f(signed_integral_types... sit) {
(cout << sit << endl)...
}
f('c', 2, 5, 7L, 10LL);
It is worth pointing out that this behavior is difficult to
get currently if you have many functions that want signed_integral_types... (or other
patterns) in their
argument lists.
Of course, this would greatly simplify implementation of std::tuple:
template<typename... Ts>
struct tuple {
Ts... value;
};
This would in no way obsolete the existing tuple, but would in fact allow
it to become much more powerful. For example, unpacking a tuple into an argument list
is a common need (See, the “more perfect forwarding example” in N3729.).
However, the implementation is extremely daunting, relying on a 40 line
metafunction
due to DRayX, whose complexity is only hinted at by its length. Unpacking
tuples into argument lists would become trivialvoid f(int, double, long);
tuple<double, long> tdl(3.4, 5L);
f(2, tdl.value...);
Likewise, explicit parameter packs would be a great base for metaprogramming
// Inherits from true_type if typelist TL includes the type T
template<<typename...> TL, typename T> struct includes;
// Inherits from true_type if typelist TL includes all the types in TL2
template<<typename...> TL, <typename...> TL2> struct contains;
// The following asserts will not fire
static_assert(includes<<double, int>, int>::value, "unexpected compile error");
static_assert(contains<<double, float, int>, <float, int>>, "unexpected compile error");
By having metafunctions return parameter packs, we can enable many use cases. For example,
if we want to generate parallel inheritance hierarchies as described in
N2965
and N3492,
we could write
// B has the same base classes as A
struct B : public direct_bases<A>::types... {}; // See N2965 for direct_bases
Since template declarations can create parsing ambiguities (see Doug Gregor's
examples), we adopt the now
familiar approach of explicitly identifying ambiguous members as parameter packstemplate<typename T>
struct same_bases_as_T : public typename<typename...> direct_bases<T>::types... {};
Sometimes, metaprogramming would be vastly simplified. For example, in Alexandrescu's
seminal book Modern C++ Design, he devotes chapter 9 to factory templates. His abstract
factory is more or less equivalent to the following code:template<typename T> struct Type2Type {};
template<typename... Ts> struct AbstractFactoryHelper;
template<> struct AbstractFactoryHelper<> {};
template<typename T, typename... Ts>
struct AbstractFactoryHelper<T, Ts...> : public AbstractFactoryHelper<Ts...> {
virtual T *doCreate(Type2Type<T>) = 0;
};
template<typename... Ts>
struct AbstractFactory : public AbstractFactoryHelper<Ts...> {
template<typename T>
T *create() {
return doCreate(Type2Type<T>());
}
}
In our “fantasy,” we would want the implementation to simply become
(also leveraging the Virtual template methods proposal)
template<typename... Ts>
struct AbstractFactory {
template<typename T> doCreate();
template<> virtual Ts *create() = 0;...
}
It's time to stop fantasizing and think about whether the fantasy can be made real. I think it can and will be demonstrating partial compiler support in Chicago. There is also need for more precise wording. For example, we need something like ->... for the same sort of reasons we need ->template.
My hope is to present this to the reflection working group for input as to whether this is a direction we would like to pursue, in which case, I will come to Seattle with a fully worded and implemented proposal.
The first question to ask of course is whether typelists belong in library or evolution. Many people have taken a library-based approach to typelists
template<typename... T> struct typelist {};
// Passing tuple's parameters to another template
template<template<typename...> class F, typename T> struct unwrap_into;
template<template<typename...> class F, typename... Ts>
struct unwrap_into<F, tuple<Ts...>> {
typedef F<Ts...> type;
};
typedef tuple<double, string> tds;
unwrap_into<map, tds>::type map_double_to_string;
By contrast, this is almost trivial when using parameter packs.
typedef<double, string> tds;
map<tds...> map_double_to_string;
What makes it worse is that how to expand the tuple's parameters can vary case by case. For example, to inherit from all of the types in a tuple, you might do something like
template<typename T> struct inherit;
template<typename... Ts>
struct inherit<tuple<Ts...>> : public Ts... {};
typedef tuple<interface1, interface2> interfaces;
struct myType : public inherit<interfaces> {};
With parameter pack-based typelists, it's no contest
typedef<interface1, interface2> interfaces;
struct myType : public interfaces... {};
struct A {
void f();
int g();
};
// Package the methods of A
typedef<&A::f, &A::g> A_methods;