template for 展开 (C++26 起)
将可以在编译期确定的语言构造展开成它的元素。
用作对多个(可能异质的)表达式应用相同逻辑的多个语句的更加可读的等价版本。
语法
属性 (可选) template for ( 初始化语句 (可选) 项声明 : 展开初始化器 ) 复合语句
|
|||||||||
| 属性 | - | 任意数量的属性 |
| 初始化语句 | - | 以下之一:
注意,所有初始化语句 必然以分号结尾。因此它经常被非正式地描述为后随分号的表达式或声明。 |
| 项声明 | - | 以下之一: |
| 展开初始化器 | - | 以下之一:
|
| 复合语句 | - | 任意复合语句 |
解释
如果需要在复合语句 中结束循环,那么可以使用 break 语句作为终止语句。
如果需要在复合语句 中结束当前迭代,那么可以使用 continue 语句作为快捷方式。
template for 语句根据展开初始化器 的语法和性质可以分为以下三类。
枚举展开语句
如果展开初始化器 是花括号包围的表达式列表,那么语句是枚举展开语句。
设 N 为表达式列表中的元素个数,/*表达式I*/ 为表达式列表的第 I 个元素(索引从 0 开始),枚举展开语句等价于下列代码,但展开初始化器 中的临时量会进行生存期扩展(见下文):
{
初始化语句
{
项声明 = /*表达式0*/;
复合语句
}
// ...
{
项声明 = /*表达式(N-1)*/;
复合语句
}
}
| 本节未完成 原因:枚举展开语句示例 1 |
表达式列表可以由包展开生成:
| 本节未完成 原因:枚举展开语句示例 2 |
迭代展开语句
如果展开初始化器 是可展开迭代的表达式(见下文),那么语句是迭代展开语句。
迭代展开语句等价于下列代码,但展开初始化器 中的临时量会进行生存期扩展(见下文),以 /* */ 包围的变量和表达式仅用于阐述:
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));
}