template for 展开 (C++26 起)

来自cppreference.com


 
 
C++ 语言
 
 

将可以在编译期确定的语言构造展开成它的元素。

用作对多个(可能异质的)表达式应用相同逻辑的多个语句的更加可读的等价版本。

语法

属性 (可选) template for ( 初始化语句 (可选) 项声明 : 展开初始化器 ) 复合语句
属性 - 任意数量的属性
初始化语句 - 以下之一:

注意,所有初始化语句 必然以分号结尾。因此它经常被非正式地描述为后随分号的表达式或声明。

项声明 - 以下之一:
展开初始化器 - 以下之一:
  • 表达式
  • 花括号包围的表达式列表,以逗号分隔,并可尾随一个逗号
复合语句 - 任意复合语句

解释

如果需要在复合语句 中结束循环,那么可以使用 break 语句作为终止语句。

如果需要在复合语句 中结束当前迭代,那么可以使用 continue 语句作为快捷方式。

template for 语句根据展开初始化器 的语法和性质可以分为以下三类。

枚举展开语句

如果展开初始化器 是花括号包围的表达式列表,那么语句是枚举展开语句

N 为表达式列表中的元素个数,/*表达式I*/ 为表达式列表的第 I 个元素(索引从 0 开始),枚举展开语句等价于下列代码,但展开初始化器 中的临时量会进行生存期扩展(见下文):

{
        初始化语句
        {
                项声明 = /*表达式0*/;
                复合语句
        }
        // ...
        {
                项声明 = /*表达式(N-1)*/;
                复合语句
        }
}

表达式列表可以由包展开生成:

迭代展开语句

如果展开初始化器 是可展开迭代的表达式(见下文),那么语句是迭代展开语句

迭代展开语句等价于下列代码,但展开初始化器 中的临时量会进行生存期扩展(见下文),以 /* */ 包围的变量和表达式仅用于阐述:

constexpr auto /*N*/ = [&] consteval
{
        std::ptrdiff_t result = 0;
        auto b = /*首表达式*/;
        auto e = /*尾表达式*/;
        for (; b != e; ++b)
                ++result;
        return result;
}();

{
        初始化语句
        constexpr(可选) decltype(auto) /*range*/ = (展开初始化器 );
        constexpr(可选) auto /*begin*/ = /*首表达式*/;
        {
                constexpr(可选) /*iter*/ = /*begin*/ + decltype(begin - begin){0};
                项声明 = */*iter*/;
                复合语句
        }
        // ...
        {
                constexpr(可选) /* iter */ = /*begin*/ + decltype(begin - begin){/*N*/ - 1};
                项声明 = */*iter*/;
                复合语句
        }
}

当且仅当项声明 具有 constexpr 说明符时,/*range*//*begin*//*iter*/ 才会被声明为 constexpr

仅用于阐述的表达式 /*首表达式*//*尾表达式*/ 定义如下:

  • 如果 /*range*/ 的类型是到数组类型 R 的引用,那么:
  • 如果 RN 个元素,那么 /*首表达式*//*range*//*尾表达式*//*range*/ + N
  • 如果 R 是边界未知或元素类型不完整的数组,那么程序非良构。
  • 如果 /*range*/ 的类型是到类类型 C 的引用,并且在 C 的作用域中对名字 “begin” 和 “end” 的查找都能各自找到至少一条声明,那么 /*首表达式*//*range*/.begin()/*尾表达式*//*range*/.end()
  • 否则 /*首表达式*/begin(/*range*/)/*尾表达式*/end(/*range*/),其中 “begin” 和 “end” 会通过实参依赖查找进行查找(不进行非实参依赖查找)。

如果展开初始化器 是非数组类型的表达式,并且上述规则可以良好定义 /*首表达式*//*尾表达式*/,那么展开初始化器 可展开迭代

解构展开语句

如果展开初始化器 是不可展开迭代的表达式,那么语句是迭代展开语句

N展开初始化器 的结构化绑定大小,解构展开语句等价于下列代码,但展开初始化器 中的临时量会进行生存期扩展(见下文),以 /* */ 包围的变量和表达式仅用于阐述:

  • 如果 N 为零,那么语句等价于:

{
        初始化语句
        constexpr(可选) auto&& /*range*/ = (展开初始化器 );
}

  • 否则语句等价于:

{
        初始化语句
        constexpr(可选) auto&& [/* u0, u1, ..., u(N-1) */] = (展开初始化器 );
        {
                项声明 = /*v0*/;
                复合语句
        }
        // ...
        {
                项声明 = /*v(N-1)*/;
                复合语句
        }
}

当且仅当项声明 具有 constexpr 说明符时,/*range*//*begin*//*iter*/ 才会被声明为 constexpr

如果展开初始化器 是左值,那么 /*vI*//*uI*/;否则 /*vI*/static_cast<decltype(/*uI*/)&&>(/*uI*/)

临时展开初始化器

对于枚举展开语句,如果在表达式列表展开初始化器 中的某个元素 expr 创建了会在 expr 的完整表达式的末尾被销毁的临时对象,那么该对象的生存期会延续到与从 expr 初始化的项声明 的生存期一致:

// 如果 foo() 按值返回
template for (auto& x : {foo().items()}) { /* ... */ }

// 会展开成:
{
    // 对于从 foo() 返回的临时对象:
    {
        auto& x = foo().items(); // 该对象会在分号处销毁,但它的生存期会延续到块的末尾
                                 // (与 “x” 的生存期相同)
        { /* ... */ }            // 在此使用 “x” 具有良好定义
    }
}

对于迭代和解构展开语句,如果在展开初始化器 中创建了会在该展开初始化器 的末尾被销毁的临时对象,那么该对象的生存期会延续到与从该展开初始化器 初始化的引用的生存期一致:

struct T
{
    std::vector<int> vec{1, 2};
    std::tuple<int> tup{3, 4};
};

template for (auto& x: T().vec) { /* ... */ }

// 会展开成:
{
    // 对于从 T() 返回的临时对象:
    decltype(auto) range = (T().vec);  // 该对象会在分号处销毁,但它的生存期会延续到
                                       // 块的末尾(与 “range” 的生存期相同)
    auto begin = range.begin();
    /* 展开的复合语句 */                 // 在此访问 vector 元素具有良好定义
}

template for (auto& x: T().tup) { /* ... */ }

// expands to:
{
    // 对于从 T() 返回的临时对象:
    auto&& [u0, u1] = T().tup; // 该对象会在分号处销毁,但它的生存期会延续到块的末尾
                               // (与 “u0” 和 “u1” 的生存期相同)
    /* 展开的复合语句 */         // 在此访问 “u0” 和 “u1” 具有良好定义
}

注解

功能特性测试宏 标准 功能特性
__cpp_expansion_statements 202506L (c++26) template for

关键词

fortemplate

示例

#include <iostream>
#include <vector>

consteval int f1(const auto&... Containers)
{
    int result = 0;
    template for (const auto& c : {Containers...}) // 枚举展开语句
    {
        result += c[0];
    }
    return result;
}

consteval int f2()
{
    constexpr std::array<int, 3> arr{1, 2, 3};
    int result = 0;
    template for (constexpr int s : arr) // 迭代展开语句
    {
        result += s;
    }
    return result;
}

struct S
{
    int i;
    short s;
};

consteval long f3(S s)
{
    long result = 0;
    template for (auto x : s) // 解构展开语句
    {
        result += sizeof(x);
    }
    return result;
}

int main()
{
    constexpr int c1[] = {1, 2, 3};
    constexpr int c2[] = {4, 3, 2, 1};
    static_assert(f1(c1, c2) == 5); // c1[0] + c2[0]
    
    static_assert(f2() == 6); // 1 + 2 + 3
    
    static_assert(f3(S{}) == sizeof(int) + sizeof(short));
}