形参包索引 (C++26 起)

来自cppreference.com
< cpp‎ | language


 
 
C++ 语言
 
 
 
 

访问处于指定索引的形参包元素。

语法

标识表达式 ...[ 表达式 ] (1)
类型定义名 ...[ 表达式 ] (2)
1) 包索引表达式
2) 包索引说明符
类型定义名 - 指名形参包名的标识符或者简单模板标识
标识表达式 - 指名形参包名的标识表达式
表达式 - std::size_t 类型的经转换常量表达式 II 在形参包 P 的范围 [0sizeof...(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) 形参包索引

示例