Is a constexpr array necessarily odr-used when subscripted?

First use of A::a:

int a = A::a[0];

The initializer is a constant expression, but that doesn’t stop A::a from being odr-used here. And, indeed, A::a is odr-used by this expression.

Starting from the expression A::a[0], let’s walk through [basic.def.odr](3.2)/3 (for future readers, I’m using the wording from N3936):

A variable x [in our case, A::a] whose name appears as a potentially-evaluated expression ex [in our case, the id-expression A::a] is odr-used unless

  • applying the lvalue-to-rvalue conversion to x yields a constant expression [it does]
    that does not invoke any non-trivial functions [it does not] and,

  • if x is an object [it is],

    • ex is an element of the set of potential results of an expression e, where either the lvalue-to-rvalue conversion is applied to e, or e is a discarded-value expression.

So: what possible values of e are there? The set of potential results of an expression is a set of subexpressions of the expression (you can check this by reading through [basic.def.odr](3.2)/2), so we only need to consider expressions of which ex is a subexpression. Those are:

A::a
A::a[0]

Of these, the lvalue-to-rvalue conversion is not applied immediately to A::a, so we only consider A::a[0]. Per [basic.def.odr](3.2)/2, the set of potential results of A::a[0] is empty, so A::a is odr-used by this expression.

Now, you could argue that we first rewrite A::a[0] to *(A::a + 0). But that changes nothing: the possible values of e are then

A::a
A::a + 0
(A::a + 0)
*(A::a + 0)

Of these, only the fourth has an lvalue-to-rvalue conversion applied to it, and again, [basic.def.odr](3.2)/2 says that the set of potential results of *(A::a + 0) is empty. In particular, note that array-to-pointer decay is not an lvalue-to-rvalue conversion ([conv.lval](4.1)), even though it converts an array lvalue to a pointer rvalue — it’s an array-to-pointer conversion ([conv.array](4.2)).

Second use of A::a:

int b  [A::a[1]];

This is no different from the first case, according to the standard. Again, A::a[1] is a constant expression, thus this is a valid array bound, but a compiler is still permitted to emit code at runtime to compute this value, and the array bound still odr-uses A::a.

Note in particular that constant expressions are (by default) potentially-evaluated expressions. Per [basic.def.odr](3.2)/2:

An expression is potentially evaluated unless it is an unevaluated operand (Clause 5) or a subexpression thereof.

[expr](5)/8 just redirects us to other subclauses:

In some contexts, unevaluated operands appear (5.2.8, 5.3.3, 5.3.7, 7.1.6.2). An unevaluated operand is not evaluated.

These subclauses say that (respectively) the operand of some typeid expressions, the operand of sizeof, the operand of noexcept, and the operand of decltype are unevaluated operands. There are no other kinds of unevaluated operand.

Leave a Comment