19

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.

0

3 Answers 3

21

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.

Sign up to request clarification or add additional context in comments.

11 Comments

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 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.
@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...)
@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.
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?
|
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.

Comments

2

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

9 Comments

@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. :-)
@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). :-)
@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?
@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.
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.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.