命名空间
变体
操作

包索引 (自 C++26 起)

来自 cppreference.com
< cpp‎ | 语言
 
 
C++ 语言
一般主题
流程控制
条件执行语句
if
迭代语句(循环)
for
范围-for (C++11)
跳转语句
函数
函数声明
lambda 函数表达式
inline 规范符
动态异常规范 (直到 C++17*)
noexcept 规范符 (C++11)
异常
命名空间
类型
规范符
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
存储持续时间规范符
初始化
 
 
 
 

访问参数包中指定索引处的元素。

内容

[编辑] 语法

id-expression ...[ expression ] (1)
typedef-name ...[ expression ] (2)
1) 包索引表达式
2) 包索引说明符
typedef-name - 一个标识符或一个简单模板标识符,它命名参数包
id-expression - 一个id-expression,它命名参数包
expression - 一个已转换常量表达式 I,类型为std::size_t,指定为索引,其中I在范围内[0sizeof...(P)),对于包索引中的某个包P

[编辑] 解释

包索引是未扩展参数包的包扩展,后面跟着省略号和下标内的索引。包索引有两种:包索引表达式和包索引说明符。

P为一个包含P0, P1, ..., Pn-1的非空参数包,I为有效索引,扩展P...[I]的实例化将产生P的包元素PI

不允许使用非常量表达式索引对包进行索引。

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]; // error: 'm' is not a constant expression
    auto d = args...[runtime_idx()]; // error: 'runtime_idx()' is not a constant expression
}

无法对模板模板参数包进行索引。

template <template <typename...> typename... Temps>
using A = Temps...[0]<>; // error: 'Temps' is a pack of template template parameters
 
template <template <typename...> typename... Temps>
using B = Temps<>...[0]; // error: 'Temps<>' doesn't denote pack name 
                         // although it is a simple-template-id

[编辑] 包索引表达式

id-expression ...[ expression ]

包索引表达式表示id-expression,即包元素PI的表达式。id-expression应由以下内容的声明引入:

template <std::size_t I, typename... Ts>
constexpr auto element_at(Ts... args)
{
    // 'args' introduced in function parameter pack declaration
    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); // error:  out of bounds
static_assert(element_at<0>() == 1); // error: out of bounds, empty pack
 
// 'Vals' introduced in non-type template parameter pack declaration
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' introduced in lambda init-capture pack
        return members...[I] + op;
    };
}
 
static_assert(foo<0>(4, "Hello", true)(5) == 9);
static_assert(foo<1>(3, std::string("C++"))("26") == "C++26");

不允许对除 id-expression 之外的复杂表达式的包进行索引。

template <std::size_t I, auto... Vals>
constexpr auto identity_at = (Vals)...[I]; // error
// use 'Vals...[I]' instead
 
template <std::size_t I, std::size_t... Vals>
constexpr std::size_t triple_at = (Vals * 3)...[I]; // error
// use 'Vals...[I] * 3' instead
 
template <std::size_t I, typename... Args>
constexpr decltype(auto) get(Args&&... args) noexcept
{
    return std::forward<Args>(args)...[I]; // error
    // use 'std::forward<Args...[I]>(args...[I])' instead
}

decltype应用于包索引表达式与将decltype应用于 id-expression 相同。

void f() 
{
    [](auto... args)
    {
        using T0 = decltype(args...[0]);   // 'T0' is 'double'
        using T1 = decltype((args...[0])); // 'T1' is 'double&'
    }(3.14);
}

[编辑] 包索引说明符

typedef-name ...[ expression ]

包索引说明符表示计算类型说明符,即包元素PI的类型。typedef-name应由类型模板参数包的声明引入。

template <typename... Ts>
using last_type_t = Ts...[sizeof...(Ts) - 1];
 
static_assert(std::is_same_v<last_type_t<>, int>); // error: out of bounds
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" is implicitly converted to 'std::string_view'
std::same_as<std::string_view> auto a = f("Hello", type_seq<std::string_view>{});
 
// Error: "Ok" is not convertible 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*...); // clearer but more permissive: Ts... can contain cv void or function types
 
void foo() 
{
    f<char, bool>(nullptr, nullptr);
    // behavior #1 (before C++26):
    //  calls void 'f<char, bool>(char*, bool*)' (aka 'f<char, bool>(char[1], bool[1])')
    // behavior #2 (since C++26): 
    //  error: supposedly called 'void f<char, bool>(bool)'
    //  but provided with 2 arguments instead of 1
 
    g<char, bool>(nullptr, nullptr);
    // calls 'g<char, bool>(char*, bool*)' (aka 'g<char, bool>(char[1], bool[1])')
 
    h<char, bool>(nullptr, nullptr);
    // calls 'h<char, bool>(char*, bool*)'
}
特性测试宏 Std 特性
__cpp_pack_indexing 202311L (C++26) 包索引

[编辑] 示例