N3728 Revision of: N3416=12-0106
2013-09-03
Mike Spertus
mike_spertus@symantec.com

Packaging Parameter Packs (Rev. 2)

Overview

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.

The fantasy

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;... }

Reality

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.

Why not just use tuple?

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

This plethora of libraries leveraging typelists testifies to their value. However, the primitive templates the typelists are based on form a weak foundation, limiting the applicability of those libraries. It is not our aim to replace these libraries, merely to give them a robust, scalable, and usable parameter pack primitive to build off. We illustrate this by comparing the “tuple as typelist” suggestion to packaged parameter packs:
Performance
Compilation speed is often gating in metaprogramming. In implementating the bases trait proposed in N3729 as a g++4.7 extension, I compared the performance of returning a tuple type giving all the base classes of a class with returning a simple typelist class containing no members and no recursive expansion. The simple typelist class was 60 times faster to compile. A built-in parameter list would presumably be even faster than that. Given that the recursive member generation in tuple provides no benefit for typelists, tuple-based typelists should probably be avoided for performance reasons alone.
Interoperability
If tuples are used as typelists, it is difficult to convert between tuples and parameter packs, meaning sometimes you will have code that expects a tuple and other times code expecting a parameter pack. For example, passing a tuple's arguments to another template's argument list is awkward. // 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... {};
Non-type parameters
A tuple cannot have non-type template parameters, so it cannot encapsulate general lists of template parameters (OK. Maybe a constexpr tuple could be used. But even that wouldn't work if some parameters are types and others are non-type parameters). Consider this example, which returns the methods of a particular class. Note that a putative reflection trait for enumerating methods would also have the same issue. struct A { void f(); int g(); }; // Package the methods of A typedef<&A::f, &A::g> A_methods;
Template expansion limits
Tuples are subject to severe template expansion limits. Under Visual Studio, a template can have at most 64 template parameters. This is insufficient for many important use cases for typelists. For example, compile-time reflection proposals often propose returning a typelist of metaobjects for all of the members of a class or all of the symbols in a namespace. This could far overrun the limits for template parameters (Just consider enumerating the symbols in namespace std).  By contrast, parameter packs are a kind of degenerate template type, which cannot have members, cannot be specialized, etc. My implementation actually generates a class with members rather than a variadic template class to avoid these limits.