Pack indexing (since C++26)
访问 参数包 中指定索引处的元素。
目录 |
语法
标识表达式
...[
表达式
]
|
(1) | ||||||||
类型定义名
...[
表达式
]
|
(2) | ||||||||
| typedef-name | - | 命名包的一个 标识符 或 简单模板标识 |
| id-expression | - | 命名包的一个 标识表达式 |
| expression | - |
在包索引中,对于某个包
P
,类型为
std::
size_t
的
转换常量表达式
I
被指定为索引,其中
I
在范围
[
0
,
sizeof...
(
P
)
)
内
|
说明
包索引是一种未展开包后接下标内省略号和索引的 包展开 。包索引分为两种类型:包索引表达式和包索引说明符。
设
P
为一个包含
P
0
, P
1
, ..., P
n-1
的非空包,且
I
为有效索引,则展开式
P...[I]
的实例化将生成包
P
中的元素
P
I
。
使用非常量表达式索引
I
对包进行索引是不允许的。
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]; // 错误:'m' 不是常量表达式 auto d = args...[runtime_idx()]; // 错误:'runtime_idx()' 不是常量表达式 }
对模板模板参数包进行索引操作是不可能的。
template <template <typename...> typename... Temps> using A = Temps...[0]<>; // 错误:'Temps' 是模板模板参数的包 template <template <typename...> typename... Temps> using B = Temps<>...[0]; // 错误:'Temps<>' 未表示包名称 // 尽管它是简单模板标识符
包索引表达式
标识表达式
...[
表达式
]
|
|||||||||
包索引表达式表示
id-expression
,即包元素
P
I
的表达式。
id-expression
应当由以下声明引入:
- 常量模板参数包 ,
- 函数参数包 ,
- lambda 初始化捕获包 ,或
- 结构化绑定包 。
template <std::size_t I, typename... Ts> constexpr auto element_at(Ts... args) { // 'args' 在函数形参包声明中引入 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); // 错误:越界访问 static_assert(element_at<0>() == 1); // 错误:越界访问,空包 template <std::size_t I, typename Tup> constexpr auto structured_binding_element_at(Tup tup) { auto [...elems] = tup; // 'elems' 在结构化绑定包声明中引入 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' 在常量模板形参包声明中引入 template <std::size_t I, std::size_t... Vals> constexpr std::size_t double_at = Vals...[I] * 2; // 正确 template <std::size_t I, typename... Args> constexpr auto foo(Args... args) { return [...members = args](Args...[I] op) { // 'members' 在lambda初始化捕获包中引入 return members...[I] + op; }; } static_assert(foo<0>(4, "Hello", true)(5) == 9); static_assert(foo<1>(3, std::string("C++"))("26") == "C++26");
除标识表达式外的复杂表达式不允许进行索引包展开。
template <std::size_t I, auto... Vals> constexpr auto identity_at = (Vals)...[I]; // 错误 // 应使用 'Vals...[I]' template <std::size_t I, std::size_t... Vals> constexpr std::size_t triple_at = (Vals * 3)...[I]; // 错误 // 应使用 'Vals...[I] * 3' template <std::size_t I, typename... Args> constexpr decltype(auto) get(Args&&... args) noexcept { return std::forward<Args>(args)...[I]; // 错误 // 应使用 'std::forward<Args...[I]>(args...[I])' }
对包索引表达式应用
decltype
等同于对标识表达式应用
decltype
。
void f() { [](auto... args) { using T0 = decltype(args...[0]); // 'T0' 为 'double' 类型 using T1 = decltype((args...[0])); // 'T1' 为 'double&' 类型 }(3.14); }
包索引说明符
typedef名称
...[
表达式
]
|
|||||||||
包索引说明符表示
计算类型说明符
,即包元素
P
I
的类型。该
类型定义名
必须通过
类型模板参数包
的声明引入。
template <typename... Ts> using last_type_t = Ts...[sizeof...(Ts) - 1]; static_assert(std::is_same_v<last_type_t<>, int>); // 错误:越界访问 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*>);
包索引说明符可以表现为:
- 一个 简单类型说明符 ,
- 一个 基类说明符 ,
- 一个 嵌套名称说明符 ,或
- 显式析构函数调用的类型 。
包索引说明符可用于函数或构造函数参数列表中,以在模板实参推导中建立 非推导上下文 。
template <typename...> struct type_seq {}; template <typename... Ts> auto f(Ts...[0] arg, type_seq<Ts...>) { return arg; } // 正常:"Hello" 隐式转换为 'std::string_view' std::same_as<std::string_view> auto a = f("Hello", type_seq<std::string_view>{}); // 错误:"Ok" 无法转换为 'int' std::same_as<int> auto b = f("Ok", type_seq<int, const char*>{});
注释
在C++26之前, Ts... [ N ] 是用于声明未命名数组参数包的有效语法,其中参数类型随后会被调整为指针类型。自C++26起, Ts... [ 1 ] 被解析为包索引说明符,这将使下列行为变为#2。如需保留第一种行为,函数参数包必须具名,或手动调整为指针类型的包。
template <typename... Ts> void f(Ts... [1]); template <typename... Ts> void g(Ts... args[1]); template <typename... Ts> void h(Ts*...); // 更清晰但更宽松:Ts... 可能包含 cv void 或函数类型 void foo() { f<char, bool>(nullptr, nullptr); // 行为 #1 (C++26 之前): // 调用 void 'f<char, bool>(char*, bool*)' (即 'f<char, bool>(char[1], bool[1])') // 行为 #2 (自 C++26 起): // 错误:预期调用 'void f<char, bool>(bool)' // 但提供了 2 个参数而非 1 个 g<char, bool>(nullptr, nullptr); // 调用 'g<char, bool>(char*, bool*)' (即 'g<char, bool>(char[1], bool[1])') h<char, bool>(nullptr, nullptr); // 调用 'h<char, bool>(char*, bool*)' }
| 功能测试宏 | 值 | 标准 | 功能 |
|---|---|---|---|
__cpp_pack_indexing
|
202311L
|
(C++26) | Pack indexing |
示例
#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)); }