Namespaces
Variants

template for expansion (since C++26)

From cppreference.com
 
 
C++ language
General topics
Flow control
Conditional execution statements
if
Iteration statements (loops)
for
range-for (C++11)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications (until C++17*)
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
explicit (C++11)
static

Special member functions
Templates
Miscellaneous
 
 

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:
  • an expression
  • a comma-separated expression list optionally with a trailing comma, enclosed in braces
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
        }
}

The expression list can be the result of a pack expansion:

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 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 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 .

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 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 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

for, template

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));
}