template for 展开 (C++26 起)
将可以在编译期确定的语言构造展开成它的元素。
用作对多个(可能异质的)表达式应用相同逻辑的多个语句的更加可读的等价版本。
语法
属性 (可选) 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的引用,那么:
- 如果
R有N个元素,那么/*首表达式*/是/*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
|
关键词
示例
#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)); }