包索引 (C++26 起)
来自cppreference.com
访问处于指定索引的包元素。
语法
标识表达式 ...[ 表达式 ]
|
(1) | ||||||||
typedef 名 ...[ 表达式 ]
|
(2) | ||||||||
1) 包索引表达式
2) 包索引说明符
| typedef 名 | - | 指名包名的标识符或者简单模板标识 |
| 标识表达式 | - | 指名包名的标识表达式 |
| 表达式 | - | 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 的表达式。标识表达式 应通过以下声明引入:
- 非类型模板形参包,
- 函数形参包,
- 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; // 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);
}
包索引说明符
typedef 名 ...[ 表达式 ]
|
|||||||||
包索引说明符表示 经计算类型说明符,即包元素 PI 的类型。typedef 名 应由类型模板形参包的声明引入。
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) | 包索引 |
示例
运行此代码
#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));
}