Why Clang 22 constexpr bindings stop before tuple types
Clang 22 implements constexpr structured bindings for arrays and aggregates. Tuple-like decomposition needs deeper changes to how constant evaluation represents local references.
Adding constexpr to a structured binding looks like a small grammar change. For an array or an aggregate, much of the work is indeed straightforward. A tuple-like object introduces references to a hidden object, and a local hidden object can occupy a different address each time its function runs. That distinction explains the implementation boundary in LLVM 22.1.0, released on February 24.
The Clang release notes say implementation of P2686R5 has started. They explicitly exclude references to constexpr variables and decomposition of tuple-like types. Arrays and aggregates are the supported cases. Understanding the proposal therefore helps explain both the new capability and why a superficially similar example can remain unsupported.
After a decade with C++, I still find RAII its strongest argument and accept that many abstractions can avoid runtime overhead. The cost often appears in compilation and in the number of language rules needed to understand an expression. This feature illustrates that tradeoff. Convenient names for constant results require rules about hidden objects and the identity of local storage, even when the program performs no runtime computation at that point.
Arrays do not require the difficult reference model
P2686R5, by Corentin Jabot and Brian Bi, explains the different decomposition models. Array and class-member bindings are modeled as special aliases instead of ordinary references. That lets the language support cases such as bit-fields. It also means the simplest constexpr decompositions do not require solving the local-reference problem first.
A small aggregate makes the supported use concrete. This example uses the C++26 language mode and the subset described in the Clang notes.
struct dimensions {
int width;
int height;
};
void check_dimensions()
{
constexpr auto [width, height] = dimensions{16, 9};
static_assert(width * height == 144);
}
The initializer supplies the hidden object introduced by the binding declaration. Each individual name identifies a member. The proposal specifies constant evaluation of a member binding in terms of access to that hidden object, so the assertion can reason about the initialized member values.
An array provides the corresponding element case.
void check_array()
{
using values = int[2];
constexpr auto [left, right] = values{2, 3};
static_assert(left + right == 5);
}
The paper uses this form to explain evaluation as access to elements of the hidden array. Naming those elements does not require storing an ordinary reference whose absolute runtime address becomes a constant-expression result. That is why this case can be available while tuple-like decomposition remains unfinished.
Tuple-like bindings expose an address question
The proposal expands a local tuple binding into a hidden constexpr tuple and references initialized through get. The hidden tuple has automatic storage duration. A reference to one of its elements consequently identifies local storage whose address depends on a particular function invocation. Simply saying that the reference initializer is a constant expression does not resolve how to preserve that identity across constant-evaluation contexts.
The issue is separate from whether the stored integer value is constant. A reference describes an object, and an object can have a known value without a single absolute address shared by every function invocation. Treating those properties as identical would either reject useful programs or admit reference results without adequate lifetime restrictions.
The authors trace this work to P1481R0 and to earlier revisions of P2686. Core review found the original wording insufficient for automatic-storage tuple bindings. The paper then evaluates several approaches instead of presenting the feature as a parser-only addition. The revision history records the choice of symbolic addressing and the subsequent refinements needed to specify it.
The proposal also retains restrictions on mutable subobjects. Its wording example rejects a static assertion that reads a mutable member through a constexpr structured binding. A declaration describing the containing object as constexpr does not make every possible access to its subobjects a permitted constant expression. That distinction keeps the feature connected to the existing constant-evaluation rules instead of granting blanket permission to every name introduced by a binding.
Making constexpr objects static would change programs
One alternative makes constexpr variables implicitly static. P2686 rejects that direction because it can change program behavior and conflict with the meaning of constexpr. Its example contains a constexpr object with a mutable member. Giving that object static storage would let mutations persist across calls, changing the result of code that currently receives a fresh local object.
Re-evaluating get whenever a tuple binding participates in constant evaluation is another possibility. The paper argues that this would address structured bindings without solving the general reference case. It could also make repeated evaluation observable with proposed compile-time facilities. Special treatment of a familiar syntax would leave related expressions with different rules.
The chosen direction instead retains which object a reference designates across constant-evaluation contexts. This symbolic identity avoids requiring an invariant absolute stack address. The wording still has to prevent references from escaping the lifetime or scope that makes that identity meaningful.
The same-function restriction keeps the model local
The proposal introduces the concept of an object being constexpr-referenceable from a particular point. For automatic storage, the relevant enclosing function scope must match. This permits useful references within one function while excluding a constexpr reference inside a lambda to an automatic variable in an enclosing function.
The lambda restriction follows from how captured access depends on the closure object. The paper explains that the compiler cannot establish the required constant-expression identity by assuming it knows which closure instance this designates. Relaxing that restriction would require additional analysis beyond the design the authors propose.
Static initialization and template arguments also retain lifetime restrictions. A constexpr reference to a local object cannot become a static reference to that object, and a reference to a function parameter cannot serve as a template argument merely because a local constexpr declaration can name it. The type of the reference does not extend the lifetime of its referent.
Clang 22 implements the array and aggregate subset while leaving these deeper reference and tuple-like cases unsupported. That boundary should remain explicit in examples and compatibility claims. The release makes constant member and element decomposition available for experiments, while the proposal supplies the object-identity model needed to understand the larger feature that the implementation has begun.