N3416=12-0106
2012-09-21
Mike Spertus
mike_spertus@symantec.com

Packaging Parameter Packs

Overview

The purpose of this paper is to propose mechanisms and rationale for named parameter packs and literal parameter packs. In particular, this proposal makes parameter packs suitable for use as typelists [1] and allows them to be defined and referenced from outside of template classes.

Proposed changes

This section informally describes our proposal. See the Wording section below for precise details.  

Rationale - Why we need language support

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 from [2] to packaged parameter packs:
Performance
Compilation speed is paramount in metaprogramming. In implementating the bases trait proposed in [3] 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 expectation, which should be discussed in Oregon) is that the (partly) new primitive parameter pack type proposed here that merely encapsulates a template-parameter-list could relax these limits.

Bonus feature: weakly specifying parameters

For routines that manipulate parameter packs structurally regardless of type, it would be nice to declare parameter packs with arbitrary parameters using a naked .... we use “.” (by analogy with “...”) to represent a single arbitrary template parameter. For example, suppose we want to calculate the length of an (unexpanded) parameter pack without resorting to the built-in sizeof... operator: template<typename T> struct length; template<> struct length<<>> { static constexpr int value = 0; }; template<. t> struct length<<t, ... Rest>> { static constexpr int value = 1 + length<Rest> }; template<typename T> struct length<<T, ... Rest>> { static constexpr int value = 1 + length<Rest> }; typedef<int, 5, double, 6> pp; static_assert(length<pp>::value == sizeof...(pp), "unexpected compile error");

It is a little clumsy to have to provide distinct partial specializations depending on whether the first parameter is a type or non-type parameter. To improve on this, The following example illustrates a template for reversing an arbitrary (expanded) parameter pack: template<... Ts> struct reverse; template<> struct reverse<> { typedef<> type; }; template<. T, ... Rest> struct reverse<T, Rest...> struct { typedef <reverse<Rest...>::type..., T> type; }; static_assert(is_same<reverse<char, int, vector<string>>::type, <vector<string>, int, char>>, "Unexpected compile error"); This also gives us a nice way of getting the ith item from a parameter pack, which Doug Gregor describes in [] as “probably the most-requested feature for variadic templates” Perhaps something along the lines of template<int i, typename T> struct at; template<int i, . T, ... Rest> struct at<i, <T, Rest...>> : public at<i - 1, Rest> {}; template<. t, ... Rest> // Notation from N3405 struct at<0, <t, Rest...>> { static constexpr T value = t; }; template<typename T, ... Rest> struct at<0, <T, Rest...>> { typedef T type; }; typedef <int, 7> int7; at<0, int7>::type i7 = at<1, int7>::value;

References

[1] Alexandrescu, Modern C++ Design: Generic Programming and Design Patterns Applied. Addison Wesley, 2001.

[2] Gregor, Järvi, Variadic Templates for C++0x; Journal of Object Technology (2008) 31-51. http://www.jot.fm/issues/issue_2008_02/article2/

[3] Spertus, Type traits and base classes, N2965=09-0155. http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2009/n2965.html.

[4] The Boost MPL Library. http://www.boost.org/doc/libs/1_51_0/libs/mpl/doc/index.html.

[5] Variadic Templates in C++0x need some additional features to come closer to fulfilling their promise. https://groups.google.com/forum/?fromgroups=#!topic/comp.std.c++/_-6X_xZlKlA.

[6] Loki Library. http://loki-lib.sourceforge.net/.

[7] “unpacking” a tuple to call a matching function pointer. http://stackoverflow.com/questions/7858817/unpacking-a-tuple-to-call-a-matching-function-pointer/.

[8] Gregor, Powell, Järvi, Typesafe Variable-Length Function and Template Argument Lists