Namespaces
Variants

Pack indexing (since C++26)

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

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

目录

语法

标识表达式 ...[ 表达式 ] (1)
类型定义名 ...[ 表达式 ] (2)
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 应当由以下声明引入:

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));
}