template for expansion (since C++26)
Expands a compile-time determinable language construct into its elements.
Used as a more readable equivalent to multiple statements that apply the same logic to multiple (possibly heterogenous) expressions.
Syntax
attr (optional) template for ( init-statement (optional) item-declaration : expansion-initializer ) compound-statement
|
|||||||||
| attr | - | any number of attributes |
| init-statement | - | one of the following:
Note that any init-statement must end with a semicolon. This is why it is often described informally as an expression or a declaration followed by a semicolon. |
| item-declaration | - | one of the following:
|
| expansion-initializer | - | one of the following:
|
| compound-statement | - | any compound statement |
Explanation
If the loop needs to be terminated within compound-statement , a break statement can be used as terminating statement.
If the current iteration needs to be terminated within compound-statement , a continue statement can be used as shortcut.
Depending on the syntax and property of expansion-initializer, a template for statement can fall into one of the following three categories.
Enumerating expansion statement
If expansion-initializer is a brace-enclosed expression list, the statement is an enumerating expansion statement .
Let N be the number of elements in the expression list, and /*expr-I*/ be the Ith element (index starts from 0) of the expression list, an enumerating expansion statement is equivalent to the following except for the lifetime expansion of temporaries of expansion-initializer (see below):
{
init-statement
{
item-declaration = /*expr-0*/;
compound-statement
}
// ...
{
item-declaration = /*expr-(N-1)*/;
compound-statement
}
}
| This section is incomplete Reason: Enumerating expansion statement example 1 |
The expression list can be the result of a pack expansion:
| This section is incomplete Reason: Enumerating expansion statement example 2 |
Iterating expansion statement
If expansion-initializer is an expansion-iterable expression (see below), the statement is an iterating expansion statement .
An iterating expansion statement is equivalent to the following except for the lifetime expansion of temporaries of expansion-initializer (see below), the variables and expressions wrapped in /* */ are for exposition only:
constexpr auto /*N*/ = [&] consteval
{
std::ptrdiff_t result = 0;
auto b = /*begin-expr*/;
auto e = /*end-expr*/;
for (; b != e; ++b)
++result;
return result;
}();
{
init-statement
constexpr(optional) decltype(auto) /*range*/ = (expansion-initializer );
constexpr(optional) auto /*begin*/ = /*begin-expr*/;
{
constexpr(optional) /*iter*/ = /*begin*/ + decltype(begin - begin){0};
item-declaration = */*iter*/;
compound-statement
}
// ...
{
constexpr(optional) /*iter*/ = /*begin*/ + decltype(begin - begin){/*N*/ - 1};
item-declaration = */*iter*/;
compound-statement
}
}
/*range*/, /*begin*/ and /*iter*/ are declared constexpr if and only if item-declaration has a constexpr specifier.
Exposition-only expressions /*begin-expr*/ and /*end-expr*/ are defined as follows:
- If the type of
/*range*/is a reference to an array typeR:
- If
Ris of boundN,/*begin-expr*/is/*range*/and/*end-expr*/is/*range*/ + N. - If
Ris an array of unknown bound or an array of incomplete type, the program is ill-formed.
- If
- If the type of
/*range*/is a reference to a class typeC, and searches in the scope ofCfor the names “begin” and “end” each find at least one declaration, then/*begin-expr*/is/*range*/.begin()and/*end-expr*/is/*range*/.end(). - Otherwise,
/*begin-expr*/isbegin(/*range*/)and/*end-expr*/isend(/*range*/), where “begin” and “end” are found via argument-dependent lookup (non-ADL lookup is not performed).
If expansion-initializer is an expression of a non-array type, and both /*begin-expr*/ and /*end-expr*/ are well-formed by the rules above, expansion-initializer is expansion-iterable .
| This section is incomplete Reason: Iterating expansion statement example |
Destructuring expansion statement
If expansion-initializer is an non-expansion-iterable expression, the statement is an iterating expansion statement .
Let N be the structured binding size of the type of expansion-initializer , an iterating expansion statement is equivalent to the following except for the lifetime expansion of temporaries of expansion-initializer (see below), the variables and expressions wrapped in /* */ are for exposition only:
- If
Nis zero, the statement is equivalent to:
{
init-statement
constexpr(optional) auto&& /*range*/ = (expansion-initializer );
}
- Otherwise, the statement is equivalent to:
{
init-statement
constexpr(optional) auto&& [/* u0, u1, ..., u(N-1) */] = (expansion-initializer );
{
item-declaration = /*v0*/;
compound-statement
}
// ...
{
item-declaration = /*v(N-1)*/;
compound-statement
}
}
/*range*/, /*begin*/ and /*iter*/ are declared constexpr if and only if item-declaration has a constexpr specifier.
If expansion-initializer is an lvalue, then /*vI*/ is /*uI*/; otherwise, /*vI*/ is static_cast<decltype(/*uI*/)&&>(/*uI*/).
| This section is incomplete Reason: Destructuring expansion statement example |
Temporary expansion initializer
For enumerating expansion statements, if a temporary object is created in an element expr of the expression list expansion-initializer and the object would be destroyed at the end of the full-expression of expr, the object persists for the lifetime of the item-declaration initialized from expr:
// if foo() returns by value
template for (auto& x : {foo().items()}) { /* ... */ }
// expands to:
{
// for the temporary object returned from foo():
{
auto& x = foo().items(); // the object would be destroyed at the semicolon,
// but its lifetime is extended to the end of the block
// (same as the lifetime of “x”)
{ /* ... */ } // using “x” here is well-defined
}
}
For iterating and destructuring expansion statements, if a temporary object is created in expansion-initializer and the object would be destroyed at the end of that expansion-initializer , the object persists for the lifetime of the reference initialized from the expansion-initializer :
struct T
{
std::vector<int> vec{1, 2};
std::tuple<int> tup{3, 4};
};
template for (auto& x: T().vec) { /* ... */ }
// expands to:
{
// for the temporary object returned from T():
decltype(auto) range = (T().vec); // the object would be destroyed at the semicolon,
// but its lifetime is extended to the end of the
// block (same as the lifetime of “range”)
auto begin = range.begin();
/* expanded compound statements */ // accessing the vector elements here
// is well-defined
}
template for (auto& x: T().tup) { /* ... */ }
// expands to:
{
// for the temporary object returned from T():
auto&& [u0, u1] = T().tup; // the object would be destroyed at the semicolon,
// but its lifetime is extended to the end of the block
// (same as the lifetimes of “u0” and “u1”)
/* expanded compound statements */ // accessing the “u0” and “u1” here
// is well-defined
}
Notes
| Feature-test macro | Value | Std | Feature |
|---|---|---|---|
__cpp_expansion_statements |
202506L |
(c++26) | template for
|
Keywords
Example
#include <iostream>
#include <vector>
consteval int f1(const auto&... Containers)
{
int result = 0;
template for (const auto& c : {Containers...}) // enumerating expansion statement
{
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) // iterating expansion statement
{
result += s;
}
return result;
}
struct S
{
int i;
short s;
};
consteval long f3(S s)
{
long result = 0;
template for (auto x : s) // destructuring expansion statement
{
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));
}