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-statementattr- any number of
init-statement- one of the following: an
(which may be a null statement ;)
a
(typically a declaration of a variable with initializer), it may declare arbitrarily many variables or be a
structured binding declaration
an
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: a
with the following restrictions: It has only one
.
The declarator must have no
.
The
declaration specifier sequence
can only contain type specifiers and constexpr, and it cannot define a
or
.
a
declaration
expansion-initializer- one of the following: an
a comma-separated expression list optionally with a trailing comma, enclosed in braces
compound-statement- any
Explanation
If the loop needs to be terminated within compound-statement , a
can be used as terminating statement.
If the current iteration needs to be terminated within compound-statement , a
can be used as shortcut.
Depending on the syntax and property of expansion-initializer, a templatefor 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
):
{
init-statement
{
item-declaration=/*expr-0*/;
compound-statement
}
// ...
{
item-declaration=/*expr-(N-1)*/;
compound-statement
}
}
The expression list can be the result of a
:
Iterating expansion statement
If expansion-initializer is an expansion-iterable expression (see
), 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
), 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 type R:
If R is of bound N, /*begin-expr*/ is /*range*/ and /*end-expr*/ is /*range*/+N.
If R is an array of unknown bound or an array of incomplete type, the program is ill-formed.
If the type of /*range*/ is a reference to a class type C, and searches in the scope of C for 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*/ is begin(/*range*/) and /*end-expr*/ is end(/*range*/), where “begin” and “end” are found via
(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 .
Destructuring expansion statement
If expansion-initializer is an non-expansion-iterable expression, the statement is an iterating expansion statement .
Let N be the
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
), the variables and expressions wrapped in /* */ are for exposition only:
If N is 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*/).
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 valuetemplatefor(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 :
structT{std::vector<int>vec{1,2};std::tuple<int>tup{3,4};};templatefor(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”)autobegin=range.begin();/* expanded compound statements */// accessing the vector elements here// is well-defined}templatefor(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 macroValueStdFeature
(c++26)templateforKeywords
,
Example
Run this code
#include<array>#include<vector>constexprintf1(constauto&...containers){intresult=0;templatefor(constauto&c:{containers...})// enumerating expansion statementresult+=c[0];returnresult;}constexprintf2(){constexprstd::array<int,3>arr{1,2,3};intresult=0;templatefor(constexprints:auto(arr))// iterating expansion statementresult+=s;returnresult;}structS{inti;shorts;};constexprlongf3(Ss){longresult=0;templatefor(autox:s)// destructuring expansion statementresult+=sizeof(x);returnresult;}intmain(){constexprintc1[]={1,2,3};constexprintc2[]={4,3,2,1};static_assert(f1(c1,c2)==5);// c1[0] + c2[0]static_assert(f2()==6);// 1 + 2 + 3static_assert(f3(S{})==sizeof(int)+sizeof(short));}