JTC1/SC22/WG21
N4235
Document number: N4235
Date: 2014-10-10
Project: Programming Language C++, Evolution Working Group
Reply-to: Daveed Vandevoorde <daveed@edg.com>
Selecting from Parameter Packs
==============================
### Introduction
Most uses of template parameter packs require the pack to be broken down into its constituent elements. For some common simple cases (like "pick the Nth argument") this requires complexity (and compiler resources) disproportionate to the task at hand.
For example:
template<int N, typename T0, typename ... Tr> struct nth_type_impl {
using type = typename nth_type_impl<N-1, Tr...>::type;
};
template<typename T0, typename ... Tr>
struct nth_type_impl<0, T0, Tr...> {
using type = T0;
};
template<int N, typename ... Ts>
using nth_type = typename nth_type_impl<N, Ts...>::type;
template<typename ... Ts> struct App {
nth_type<3, Ts...> n1; // Pick the 4th argument.
}
While this works, `nth_type` has a few weaknesses:
* It's expensive in terms of compiler resources (e.g., template instantiations use memory that is never freed during the compilation process).
* Diagnostics are likely to be overly verbose.
(Something similar to `nth_type` can be done for packs of nontypes and packs of templates.)
See also EWG "[tiny]" issue 30, which this proposal addresses, and N3761 which proposes to standardize facilities such as `nth_type` in the library.
### Simple Selection
Given the weaknesses of C++11/C++14-based solutions, we propose a compact notation to select a single element from a parameter pack instantiation. With that notation, the `App` example above becomes:
template<typename ... Ts> struct App {
Ts.[3] n1; // Pick the 4th argument.
}
It is tempting to replace `Ts.[3]` by just `Ts[3]`, but that leads to ambiguities. For example:
For example:
template<typename ... Ts, int ... Ns>
std::size_t f() {
sum(sizeof(Ts[Ns])...);
// size of Ts[Ns] for each pair or size of Ns-th element of Ts?
}
As one might expect, the simple selection syntax applies to packs of types, packs of nontypes, and packs of templates.
Attempting to select an element that doesn't exist is invalid (and error, or in SFINAE contexts, a deduction failure):
template<int ... Ns>
constexpr int first() { return Ns.[0]; }
int x = first<>(); // Error.
### Pack Subsetting
Besides selecting a single element from a pack, it is often also useful to select subsets of parameter packs. We propose to enable such subsetting using a notation similar to the above with the "subscript" a pack expansion or a list of two or more integral constant expressions. Specifically, assuming `Ts` and `Ns` are parameter packs
Ts.<Ns...>
produces a new pack by selecting from pack `Ts` the elements numbered by pack `Ns`. Note that if `Ns` is an empty pack the result is an empty pack, and that if `Ns` has a single element the result is still a pack containing one element (and not just that element). (We could have used a bracket-based notation `Ts.[Ns...]`, but that requires disambiguation rules and makes it a little harder for humans to distinguish simple selection from pack subsetting.)
Similarly,
Ts.<1, 3, 5>
selects the second, fourth, and sixth element of `Ts`, and
Ts.<0, Ns..., 0>
produces a pack with the first element of `Ts` inserted both at the front and the back of the pack produced by `Ts.<Ns...>`.
Finally, we propose a new shorthand to create integer sequence pack expansions:
b ...< e integer sequence b, b+1, b+2, ... < e
(`b`, `e` denote nonnegative integral constant-expressions.) This corresponds to semi-open intervals, which includes the possibility of empty pack expansions and is broadly the most convenient interval form.
This sequence notation can appear anywhere a pack expansion producing a sequence of integers is valid. For example:
int digits = { 0 ...< 10 };
// Same as: int digits = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
However, it is most useful with pack subsetting. For example,
Ts.<1 ...< sizeof...(Ts)>
produces the pack `Ts` without its first element, and
Ts.<0 ...< sizeof...(Ts)-1>
produces the pack `Ts` without its last element.
As with the `Ts.<0, Ns..., 0>` example above, integer sequence packs can be composed with commas. For example,
Ts.<(0 ...< sizeof...(Ts)), (0 ...< sizeof...(Ts))>
produces a pack that repeats the elements of pack `Ts`.
As with simple selection, attempting to select a subset that includes non-existing elements (e.g., `Ts.<(1 ...< sizeof...(Ts)+1)>`) is ill-formed.
To avoid surprises, we also propose a "lexer hack" that makes _pp-number_ stop before a double period. That would make `1...<3` valid and equivalent to the form with inserted spaces (i.e., `1 ...< 3`).
### Acknowledgments
Thanks to Maurice Bos, Doug Gregor, Mikael Kilpeläinen, Richard Smith, and Ville Voutainen for discussion on this topic.