17

Given the following code:

struct A { static constexpr int a[3] = {1,2,3}; };

int main () {
  int a = A::a[0];
  int b  [A::a[1]];
}

is A::a necessarily odr-used in int a = A::a[0]?


Note: This question represents a less flamey/illogical/endless version of a debate in the Lounge.

Community
  • 1
  • 1
Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055

3 Answers3

20

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.

Richard Smith
  • 13,696
  • 56
  • 78
  • It would be nice if you could also provide some rationale: Why is a definition required for something that is (seems to be) a compile-time constant? How is `int b[A::a[1]];` different from `struct A { static constexpr auto a = 5; }; int b[A::a];`? – dyp May 02 '14 at 20:59
  • 1
    @dyp I would expect that the difference is in the very letter of the spec as it has been reproduced here. Nothing more. Seems a logical explanation to me. – sehe May 02 '14 at 21:02
  • 1
    @dyp I don't think we really considered this when discussing the wording that became [basic.def.odr]p2, but it certainly makes sense to me to keep those rules simple. An implementation has to defer certain actions in order to support the current odr-use rules; if we added array indexing to the mix, implementations would also have to defer converting `A[I]` to `*(A + I)`, because they'd behave differently. (We've already ventured into these waters by making `rvalue[I]` an xvalue, so maybe now is a better time to consider this change...) – Richard Smith May 02 '14 at 21:16
  • 2
    @RichardSmith: Personally, I don't see why `*(A + I)` would require ODR-use either. The run-time address of A has no bearing on the result of the expression. Whether or not the Standard supports it is another question, of course, but conceptually, I would argue that it should not be ODR-used. – Puppy May 02 '14 at 21:53
  • Can you clarify one thing? you say that "The set of potential results of an expression is a set of subexpressions of the expression" and then you state that `A::a` is a subexpression of `A::a[0]` (as also of `A::a`). Afterwards you say "the set of potential results of `A::a[0]` is empty" but didn't you just say that `A::a` is a subexpression (hence a potential result) of `A::a[0]`? Why it's empty now? – oblitum May 03 '14 at 05:14
  • @pepper_chico The set of potential results is a set of subexpressions, but it's not the set of *all* subexpressions. For `A::a[0]`, the set of potential results does not include `A::a`. A better example would be something like `b ? (x) : y.foo`, whose set of potential results is {`x`, `y`}. – Richard Smith May 04 '14 at 19:59
  • @RichardSmith I don't understand why the potential result does not include `y.foo`. Isn't it even more of a result of the expression than `y`? I guess I don't understand the set of things that the "potential results" is intended to capture. – Johannes Schaub - litb May 06 '14 at 15:36
  • @litb The "set of potential results" of `e` essentially means the set of *id-expressions* that are not odr-used if an lvalue-to-rvalue conversion is applied to `e` (and an lvalue-to-rvalue conversion applied to the *id-expression* directly would be a constant expression). – Richard Smith May 06 '14 at 20:05
  • n3936 is substantially different from C++11; it is well on the way to C++14. This is a good answer, but I think it isn't answering the right question. – ecatmur May 21 '14 at 15:05
  • @DeadMG yes, this is a defect in the current C++14 draft. See http://stackoverflow.com/questions/23491781/when-is-a-variable-odr-used-in-c14?lq=1 – ecatmur May 21 '14 at 15:05
  • @ecatmur The current wording was added by a core issue with DR status, which makes it a de facto part of C++11 implementations (even though it's not part of ISO C++11). – Richard Smith Jun 08 '14 at 20:51
6

Yes, A::a is odr-used.

In C++11, the relevant wording is 3.2p2 [basic.def.odr]:

[...] A variable whose name appears as a potentially-evaluated expression is odr-used unless it is an object that satisfies the requirements for appearing in a constant expression (5.19) and the lvalue-to-rvalue conversion (4.1) is immediately applied. [...]

The name of the variable A::a appears in the declaration int a = A::a[0], in the full-expression A::a[0], which is a potentially-evaluated expression. A::a is:

  • an object
  • that satisfies the requirements for appearing in a constant expression

However, the lvalue-to-rvalue conversion is not immediately applied to A::a; it is applied to the expression A::a[0]. Indeed, lvalue-to-rvalue conversion may not apply to an object of array type (4.1p1).

So A::a is odr-used.


Since C++11, the rules have been broadened somewhat. DR712 Are integer constant operands of a conditional-expression "used?" introduces the concept of the set of potential results of an expression, which allows expressions such as x ? S::a : S::b to avoid odr-use. However, while the set of potential results respects such operators as the conditional operator and comma operator, it does not respect indexing or indirection; so A::a is still odr-used in the current drafts for C++14 (n3936 as of date).

[I believe this is a condensed equivalent to Richard Smith's answer, which however does not mention the change since C++11.]

At When is a variable odr-used in C++14? we discuss this issue and possible wording changes to section 3.2 to allow indexing or indirecting an array to avoid odr-use.

Community
  • 1
  • 1
ecatmur
  • 152,476
  • 27
  • 293
  • 366
1

No, it is not odr-used.

First, both your array and its elements are of literal type:

[C++11: 3.9/10]: A type is a literal type if it is:

  • a scalar type; or
  • a class type (Clause 9) with
  • a trivial copy constructor,
  • no non-trivial move constructor,
  • a trivial destructor,
  • a trivial default constructor or at least one constexpr constructor other than the copy or move constructor, and
  • all non-static data members and base classes of literal types; or
  • an array of literal type.

Now we look up the odr-used rules:

[C++11: 3.2/2]: [..] A variable or non-overloaded function whose name appears as a potentially-evaluated expression is odr-used unless it is an object that satisfies the requirements for appearing in a constant expression (5.19) and the lvalue-to-rvalue conversion (4.1) is immediately applied. [..]

And here we've been referred to the rules on constant expressions, which contain nothing prohibiting your initialiser from being a constant expression; the pertinent passages are:

[C++11: 5.19/2]: A conditional-expression is a constant expression unless it involves one of the following as a potentially evaluated subexpression [..]:

  • [..]
  • an lvalue-to-rvalue conversion (4.1) unless it is applied to
    • a glvalue of integral or enumeration type that refers to a non-volatile const object with a preceding initialization, initialized with a constant expression, or
    • a glvalue of literal type that refers to a non-volatile object defined with constexpr, or that refers to a sub-object of such an object, or
    • a glvalue of literal type that refers to a non-volatile temporary object initialized with a constant expression;
  • [..]

(Don't be put off by the name of the production, "conditional-expression": it is the only production of constant-expression and is thus the one we're looking for.)

Then, thinking about the equivalence of A::a[0] to *(A::a + 0), after the array-to-pointer conversion you have an rvalue:

[C++11: 4.2/1]: An lvalue or rvalue of type "array of N T" or "array of unknown bound of T" can be converted to a prvalue of type "pointer to T". The result is a pointer to the first element of the array.

Your pointer arithmetic is then performed on this rvalue and the result is also an rvalue, used to initialise a. No lvalue-to-rvalue conversion here whatsoever, so still nothing violating "the requirements for appearing in a constant expression".

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • 3
    @Yakk: ¶7: _"Subscriber is solely responsible for any use of or action taken under Subscriber’s password and accepts full responsibility for all activity conducted through Subscriber’s account and agrees to and hereby releases the Network and Stack Exchange from any and all liability concerning such activity. Subscriber agrees to notify Stack Exchange immediately of any actual or suspected loss, theft, or unauthorized use of Subscriber’s account or password."_ Nothing in the ToS prohibits me from granting that authorization to any human, feline or Vulcan that I choose. :-) – Lightness Races in Orbit May 02 '14 at 13:24
  • 1
    @Nikos: Can't downvote posts from the same account, and my cat is under 13 so has to share mine (per ¶1 of the ToS, linked above). :-) – Lightness Races in Orbit May 02 '14 at 13:29
  • I agree, that `A::a[0]` satisfies the requirements of a constant expression. But I would argue, that `A::a` is _odr-used_ by §5.3.1/1: The result of indirection is an **lvalue**. To initialize `a` an lvalue-to-rvalue conversion is needed. – MWid May 02 '14 at 16:33
  • 1
    @MWid: Does `a glvalue of literal type that refers to a non-volatile object defined with constexpr, or that refers to a sub-object of such an object` not aptly describe said conversion? – Lightness Races in Orbit May 02 '14 at 18:26
  • The array appears in a potentially evaluated context and no lvalue to rvalue conversion is applied, so the array is odr-used. Your answer says that this is not the case, so where do we disagree? – Johannes Schaub - litb May 02 '14 at 20:40
  • 5
    @LightnessRacesinOrbit I downvoted because the answer is incorrect. In the C++11 rules, the lvalue-to-rvalue conversion is not "immediately applied" to the variable, therefore it is odr-used. – Richard Smith May 02 '14 at 21:23
  • @JohannesSchaub-litb I think LRiO is (incorrectly) treating array-to-pointer conversion as an lvalue-to-rvalue conversion. (See my answer.) – ecatmur May 21 '14 at 15:07
  • @ecatmur: The last standard passage I quote in my answer explicitly states that the result of array-to-pointer conversion is an rvalue. I then say "no lvalue-to-rvalue conversion here whatsoever". So, no, I don't think `LRiO is treating array-to-pointer conversion as an lvalue-to-rvalue conversion` can be true. – Lightness Races in Orbit May 21 '14 at 15:09
  • 1
    Sorry, my misunderstanding; in that case the flaw in your reasoning is that, as Richard Smith says, the lvalue-to-rvalue conversion is not "immediately applied" to the variable `A::a`. – ecatmur May 21 '14 at 15:18