August 25, 2026·4 min read·engineeringcppclangtemplatescxx26

Clang 23 begins expanding heterogeneous statements

Clang 23 partially implements C++26 expansion statements. Expression lists and destructuring expose why the feature is more than an unrolled runtime loop.

A runtime loop gives its element variable one type. A statement expanded over a heterogeneous tuple needs a separately instantiated declaration for each element. Existing C++ libraries commonly express that through templates and lambdas, which means a return inside the repeated operation returns from the lambda. Expansion statements provide a language construct whose body remains part of the surrounding function.

LLVM 23.1.0, released on August 25, begins implementing that facility. The Clang release notes describe partial P1306R5 support. Iterating expansion statements cannot yet be expanded and produce a diagnostic, while other expansion-statement kinds work. The same release introduces experimental C++2d modes, separately from its continuing C++26 implementation work.

A decade of C++ performance work has left me sympathetic to abstractions that disappear at runtime and less sympathetic to the effort needed to decipher their failures. Expansion statements address an identifiable part of that cost. Repetition over different types becomes a statement in the language, with its control flow specified directly, instead of requiring every library to reconstruct the operation through callable objects.

Expansion instantiates a sequence of bodies

P1306R5, by Dan Katz, Andrew Sutton, Sam Goodrick, Daveed Vandevoorde and Barry Revzin, uses template for syntax. The compiler determines an expansion size and instantiates the body with a corresponding declaration for each element. The repeated body is treated as a template definition for lookup and instantiation purposes.

This example uses an expression list, one of the forms distinct from the unsupported iterating case.

template<class... Values>
int total_size(Values... values)
{
    int result = 0;
    template for (auto value : {values...}) {
        result += sizeof(value);
    }
    return result;
}

Each expanded declaration can deduce a different type for value. The braces introduce the expansion expression list. They do not require all arguments to become elements of one homogeneous runtime container. The example is intended to illustrate syntax and per-element typing, with small argument types whose total size fits the chosen result type.

The expression-list design also resolves an ambiguity in earlier proposals. A bare parameter pack could mean expanding over its arguments or, inside another expansion, iterating over the elements of a particular argument. Explicit braces separate the list of expressions from the single-expression forms. They also permit prefixes, suffixes and multiple packs in the same expansion list.

Destructuring uses the structured-binding model

A destructuring expansion obtains its size from the structured binding size of the initializer type. Its specified transformation introduces a hidden structured binding and then uses the corresponding bound object for each instantiation. This lets tuple-like objects and destructurable classes participate without requiring one common element type.

That connection explains why structured-binding implementation work matters to expansion statements. Clang 23 also propagates constinit and constexpr in bindings with tuple-like initializers, according to the release notes. These are related areas of compiler machinery, although the notes do not claim that the entire constexpr-reference feature set is now complete.

A range has a different meaning from destructuring. For a range that is also tuple-like, the proposal gives priority to the range interpretation. A subrange, for example, can mean either the sequence between two iterators or the two iterator objects themselves. Choosing ranges first preserves the sequence interpretation instead of unexpectedly visiting the representation of the range object.

This priority also explains why a type that happens to support both interpretations needs attention in an experiment. A successful destructuring example does not establish support for an iterating expansion over a different type with range operations. Clang 23 explicitly documents the latter as unsupported in this implementation stage.

The declaration inside the expansion also controls whether each element is copied or accessed by reference. P1306 illustrates auto& over a tuple, producing separate reference declarations for its differently typed elements. Changing the declaration to plain auto changes that initialization. Replacing a library visitation call therefore requires preserving the intended reference behavior, as well as obtaining the same number of expanded bodies.

Compile-time range expansion has storage requirements

The range form requires a constant-expression range and determines how many bodies to instantiate during translation. P1306 describes why simply returning a vector of reflected members does not solve the problem. Dynamic allocation that survives constant evaluation would be needed to preserve that vector in the naive form.

The authors experimented with relaxing the allocation restriction, but encountered substantial implementation difficulties in the Clang constant evaluator. The revised design instead uses ordinary constant-expression restrictions. A helper such as define_static_array can convert the reflected sequence into persistent static storage suitable for expansion without requiring general non-transient constexpr allocation.

This is a useful account of implementation experience changing a language design. The supported semantics no longer depend on a narrow allocation exception created specifically for expansion statements. The source spells out the persistent sequence, and the compiler can apply the existing constant-evaluation model. Clang 23 still does not implement that iterating form, so the proposal example describes the language design rather than a working release-specific recipe.

Control flow stays in the surrounding function

The proposal permits break and continue with execution-time meanings. Break exits after the last expansion, and continue proceeds to the next expanded body. They do not instruct the compiler to stop instantiating later bodies. That distinction separates runtime control flow from the compile-time generation of the statement sequence.

Library approaches based on lambdas change the context of return and complicate coroutine use. A language statement avoids introducing that callable boundary merely to express repetition. It still has explicit control-flow restrictions, including a prohibition on identifier labels inside the expansion statement. Direct syntax therefore comes with a specified model, rather than unrestricted textual duplication.

The experimental -std=c++2d and -std=gnu++2d options in this release concern the next cycle of language implementation work. They do not change the reported status of P1306. For Clang 23 users, the concrete addition is partial C++26 statement expansion, with a documented boundary between expression-list or destructuring forms and iterating ranges. That boundary makes it possible to select an example that exercises the implemented mechanism without attributing the whole proposal to this release.