Is a constexpr array necessarily odr-used when subscripted?

前端 未结 3 1239
情深已故
情深已故 2020-11-28 11:03

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

相关标签:
3条回答
  • 2020-11-28 11:16

    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.

    0 讨论(0)
  • 2020-11-28 11:16

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

    0 讨论(0)
  • 2020-11-28 11:28

    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.

    0 讨论(0)
提交回复
热议问题