形参包索引 (C++26 起)
来自cppreference.com
访问处于指定索引的形参包元素。
语法
标识表达式 ...[ 表达式 ]
|
(1) | ||||||||
类型定义名 ...[ 表达式 ]
|
(2) | ||||||||
1) 包索引表达式
2) 包索引说明符
类型定义名 | - | 指名形参包名的标识符或者简单模板标识 |
标识表达式 | - | 指名形参包名的标识表达式 |
表达式 | - | std::size_t 类型的经转换常量表达式 I,I 在形参包 P 的范围 [ 0, sizeof...(P)) 内作为指示展开包的索引
|
解释
包索引是 包展开,未展开形参包后随省略号和下标内的索引。有两种包索引:包索引表达式和包索引说明符。
令 P
是包含 P0, P1, ..., Pn-1
的非空形参包,且 I
是一个有效索引,那么展开 P...[I]
的实例化将产生 P
的包元素 PI
。
不允许使用非常量表达式的索引 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]<>; // 错误: 'Temp' 是模板模板形参包 template <template <typename...> typename... Temps> using B = Temps<>...[0]; // 错误:'Temps<>' 不表示包名,即使它是简单模板标识
包索引表达式
标识表达式 ...[ 表达式 ]
|
|||||||||
包索引表达式表示 标识表达式,即包的元素 PI
的表达式。标识表达式 应通过以下声明引入:
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); // 错误:超出范围,空包 // 'Vals' 在非类型模板形参包声明中引入 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' 在 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); }
包索引说明符
类型定义名 ...[ 表达式 ]
|
|||||||||
包索引说明符表示 经计算类型说明符,即包元素 PI
的类型。类型定义名 应由类型模板形参包的声明引入。
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; } // OK:"Hello" 被隐式转换到 'std::string_view' std::same_as<std::string_view> auto a = f("Hello", type_seq<std::string_view>{}); // 错误:"Ok" 不能转换为 to 'int' std::same_as<int> auto b = f("Ok", type_seq<int, const char*>{});
注解
在 C++26 之前,Ts...[N] 是声明大小为 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[1], bool[1])'(即 '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) | 形参包索引 |
示例
本节未完成 原因:example |