Pack indexing (since C++26)
Greift auf das Element eines Packs an einem bestimmten Index zu.
Inhaltsverzeichnis |
Syntax
id-expression
...[
expression
]
|
(1) | ||||||||
typedef-name
...[
expression
]
|
(2) | ||||||||
| typedef-name | - | ein Identifier oder eine simple-template-id , die einen Pack benennt |
| id-expression | - | eine id-expression , die einen Pack benennt |
| expression | - |
ein
converted constant expression
I
vom Typ
std::
size_t
, der als Index festgelegt ist, wobei
I
innerhalb des Bereichs
[
0
,
sizeof...
(
P
)
)
für einen Pack
P
in der Pack-Indizierung liegt
|
Erklärung
Pack-Indexing ist eine Pack-Expansion des nicht expandierten Packs, gefolgt von einer Ellipse und einem Index innerhalb des Subscripts. Es gibt zwei Arten von Pack-Indexing: Pack-Indexing-Ausdruck und Pack-Indexing-Spezifizierer.
Sei
P
ein nicht-leerer Pack, der
P
0
, P
1
, ..., P
n-1
enthält, und
I
ein gültiger Index. Die Instanziierung der Expansion
P...[I]
ergibt das Pack-Element
P
I
von
P
.
Das Indizieren eines Packs mit einem nicht-konstanten Ausdrucksindex
I
ist nicht erlaubt.
int runtime_idx(); void bar(auto... args) { auto a = args...[0]; const int n = 1; auto b = args...[n]; int m = 2; auto c = args...[m]; // Fehler: 'm' ist kein konstanter Ausdruck auto d = args...[runtime_idx()]; // Fehler: 'runtime_idx()' ist kein konstanter Ausdruck }
Das Indizieren eines Packs von Template-Template-Parametern ist nicht möglich.
template <template <typename...> typename... Temps> using A = Temps...[0]<>; // Fehler: 'Temps' ist ein Pack von Template-Template-Parametern template <template <typename...> typename... Temps> using B = Temps<>...[0]; // Fehler: 'Temps<>' bezeichnet keinen Pack-Namen // obwohl es sich um eine Simple-Template-ID handelt
Pack-Indexierungsausdruck
id-expression
...[
expression
]
|
|||||||||
Pack-Indexierungsausdruck bezeichnet den
id-expression
, den Ausdruck des Pack-Elements
P
I
. Der
id-expression
muss durch die Deklaration von eingeführt werden:
- konstanter Template-Parameter-Pack ,
- Funktionsparameter-Pack ,
- Lambda-Init-Capture-Pack , oder
- Structured-Binding-Pack .
template <std::size_t I, typename... Ts> constexpr auto element_at(Ts... args) { // 'args' in Funktionsparameter-Pack-Deklaration eingeführt return args...[I]; } static_assert(element_at<0>(3, 5, 9) == 3); static_assert(element_at<2>(3, 5, 9) == 9); static_assert(element_at<3>(3, 5, 9) == 4); // Fehler: Außerhalb der Grenzen static_assert(element_at<0>() == 1); // Fehler: Außerhalb der Grenzen, leeres Pack template <std::size_t I, typename Tup> constexpr auto structured_binding_element_at(Tup tup) { auto [...elems] = tup; // 'elems' in strukturierter Bindungs-Pack-Deklaration eingeführt return elems...[I]; } struct A { bool a; int b; }; static_assert(structured_binding_element_at<0>(A {true, 4}) == true); static_assert(structured_binding_element_at<1>(A {true, 4}) == 4); // 'Vals' in konstanter Template-Parameter-Pack-Deklaration eingeführt template <std::size_t I, std::size_t... Vals> constexpr std::size_t double_at = Vals...[I] * 2; // OK template <std::size_t I, typename... Args> constexpr auto foo(Args... args) { return [...members = args](Args...[I] op) { // 'members' in Lambda-Init-Capture-Pack eingeführt return members...[I] + op; }; } static_assert(foo<0>(4, "Hello", true)(5) == 9); static_assert(foo<1>(3, std::string("C++"))("26") == "C++26");
Die Indizierung von komplexen Ausdrücken außer Id-Ausdrücken ist nicht erlaubt.
template <std::size_t I, auto... Vals> constexpr auto identity_at = (Vals)...[I]; // Fehler // Verwende 'Vals...[I]' stattdessen template <std::size_t I, std::size_t... Vals> constexpr std::size_t triple_at = (Vals * 3)...[I]; // Fehler // Verwende 'Vals...[I] * 3' stattdessen template <std::size_t I, typename... Args> constexpr decltype(auto) get(Args&&... args) noexcept { return std::forward<Args>(args)...[I]; // Fehler // Verwende 'std::forward<Args...[I]>(args...[I])' stattdessen }
Die Anwendung von
decltype
auf Pack-Indexierungsausdrücke ist identisch mit der Anwendung von
decltype
auf Id-Ausdrücke.
void f() { [](auto... args) { using T0 = decltype(args...[0]); // 'T0' ist 'double' using T1 = decltype((args...[0])); // 'T1' ist 'double&' }(3.14); }
Pack-Indexierungs-Spezifizierer
Typname
...[
Ausdruck
]
|
|||||||||
Der Pack-Indexierungs-Spezifizierer bezeichnet den
computed-type-specifier
, den Typ des Pack-Elements
P
I
. Der
typedef-name
muss durch die Deklaration eines
type template parameter pack
eingeführt werden.
template <typename... Ts> using last_type_t = Ts...[sizeof...(Ts) - 1]; static_assert(std::is_same_v<last_type_t<>, int>); // Fehler: Außerhalb der Grenzen static_assert(std::is_same_v<last_type_t<int>, int>); static_assert(std::is_same_v<last_type_t<bool, char>, char>); static_assert(std::is_same_v<last_type_t<float, int, bool*>, bool*>);
Der Pack-Indexierungsspezifizierer kann auftreten als:
- ein simple type specifier ,
- ein base class specifier ,
- ein nested name specifier , oder
- der type of an explicit destructor call .
Der Pack-Indexierungs-Spezifizierer kann in Funktions- oder Konstruktorparameterlisten verwendet werden, um nicht abgeleitete Kontexte in der Template-Argumentableitung zu etablieren.
template <typename...> struct type_seq {}; template <typename... Ts> auto f(Ts...[0] arg, type_seq<Ts...>) { return arg; } // OK: "Hello" wird implizit zu 'std::string_view' konvertiert std::same_as<std::string_view> auto a = f("Hello", type_seq<std::string_view>{}); // Fehler: "Ok" ist nicht zu 'int' konvertierbar std::same_as<int> auto b = f("Ok", type_seq<int, const char*>{});
Hinweise
Vor C++26 war Ts... [ N ] eine gültige Syntax zur Deklaration eines Funktionsparameter-Packs unbenannter Arrays der Größe N , wobei die Parametertypen anschließend zu Zeigern angepasst wurden. Seit C++26 wird Ts... [ 1 ] als Pack-Index-Spezifizierer interpretiert, was das nachfolgende Verhalten zu #2 ändern würde. Um das erste Verhalten beizubehalten, muss das Funktionsparameter-Pack benannt oder manuell zu einem Pack von Zeigertypen angepasst werden.
template <typename... Ts> void f(Ts... [1]); template <typename... Ts> void g(Ts... args[1]); template <typename... Ts> void h(Ts*...); // klarer, aber permissiver: Ts... kann cv void oder Funktionstypen enthalten void foo() { f<char, bool>(nullptr, nullptr); // Verhalten #1 (vor C++26): // ruft 'f<char, bool>(char*, bool*)' auf (alias 'f<char, bool>(char[1], bool[1])') // Verhalten #2 (seit C++26): // Fehler: vermeintlich 'void f<char, bool>(bool)' aufgerufen // aber mit 2 Argumenten statt 1 bereitgestellt g<char, bool>(nullptr, nullptr); // ruft 'g<char, bool>(char*, bool*)' auf (alias 'g<char, bool>(char[1], bool[1])') h<char, bool>(nullptr, nullptr); // ruft 'h<char, bool>(char*, bool*)' auf }
| Feature-Test-Makro | Wert | Std | Feature |
|---|---|---|---|
__cpp_pack_indexing
|
202311L
|
(C++26) | Pack-Indexierung |
Beispiel
#include <tuple> template <std::size_t... Indices, typename Decomposable> constexpr auto splice(Decomposable d) { auto [...elems] = d; return std::make_tuple(elems...[Indices]...); } struct Point { int x; int y; int z; }; int main() { constexpr Point p { .x = 1, .y = 4, .z = 3 }; static_assert(splice<2, 1, 0>(p) == std::make_tuple(3, 4, 1)); static_assert(splice<1, 1, 0, 0>(p) == std::make_tuple(4, 4, 1, 1)); }