19
votes

Is the following C++11 program ill-formed?

const int x[] = {1,2,3};

static_assert(x[0] == 1, "yay");

int main() {}

gcc and clang seem to think so, but why isn't x[0] == 1 a constant expression?

x[0] == 1
subscript operator
*(x+0) == 1
array-to-pointer conversion (int* p = x)
*(p+0) == 1
pointer addition
*p == 1
indirection (lvalue y = x[0])
y == 1
lvalue-to-rvalue conversion:

a non-volatile glvalue (yes, x[0] is a glvalue and non-volatile) of integral (yes it has type const int) or enumeration type that refers to a non-volatile const object (yes it has type const int) with a preceding initialization (yes initialized with 1), initialized with a constant expression (yes 1 is constant expression)

Seems true, the first element of the x array satisfies these conditions.

1 == 1

?

Is this a compiler bug, standard defect, or am i missing something?

What part of 5.19 [expr.const] says this isn't a constant expression?

2
That seems to be what this question yesterday was about: stackoverflow.com/questions/18878427us2012
It's partially related but not a dupe. In fact I am asking this question after studying that one. I think the answer may be wrong - but the difference here is that x[0] has integral type so the lvalue-to-rvalue conversion should be allowed, but it still isn't.Andrew Tomazos
Guys, it isn't a dupe! The answer doesn't apply here! This looks like a compiler bug or standard defect.Andrew Tomazos
Retracted the dupe vote. (Good thing you can do that now!)us2012
I don't know what part of the standard backs it up, but in general, constexpr has always been required to use a constant array element as a compile-time constant. You can't use a constant array element as a case or as a template parameter either, unless you use constexpr, and I believe this is considered normal. Maybe you've found a place where the spec mis-specifies this behavior?StilesCrisis

2 Answers

11
votes

In 5.19:

A [...]expression is a constant expression unless it involves one of the following [...]:

  • 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

Putting it plainly, lvalue-to-rvalue conversion can only be done in constant expressions if:

  • a constant integral (or enum) declaration initialized with a constant: const int x = 3;.
  • a declaration with constexpr: constexpr int x[] = {1,2,3};.
  • a temporary object initialized with a constant expression...

Your example does include lvalue-to-rvalue conversion, but has none of these exceptions, so x is not a constant expression. If, however, you change it to:

constexpr int x[] = {1,2,3};

static_assert(x[0] == 1, "yay");

int main() {}

Then all is well.

4
votes

By the current wording of the standard it is a compiler bug, and the program is well-formed. It is now being considered whether it should be a standard defect, as it would be difficult to implement.

For a detailed explanation see:

https://groups.google.com/a/isocpp.org/forum/?fromgroups#!topic/std-discussion/Nv5_2dCHm6M

Report copied below:

The current wording of the C++11 official through to N3690 inclusive has the following:

A conditional-expression e is a core constant expression unless the evaluation of e would evaluate one of the following expressions:

  • an lvalue-to-rvalue conversion (4.1) unless it is applied to
    • a non-volatile glvalue of integral or enumeration type that refers to a non-volatile const object with a preceding initialization, initialized with a constant expression

The following declaration at global scope:

const int x[2] = {42, 43};

defines an array of 2 const int objects, list-initialized with {42, 43}

In 8.5.1 [dcl.init.aggr]/2:

When an aggregate is initialized by an initializer list, as specified in 8.5.4, the elements of the initializer list are taken as initializers for the members of the aggregate, in increasing subscript or member order.

So the initializer of the first element object is 42 and the initializer of the second element object is 43.

The expression *x is an lvalue and a core constant expression. It entails an array-to-pointer conversion, and an indirection - neither of which disqualify the expression as a core constant expression. The glvalue expression refers to the first element object of x. *x is a non-volatile glvalue of integral type (const int) that refers to a non-volatile const object with a preceding initialization, and intialized with the constant expression 42.

Therefore an lvalue-to-rvalue conversion applied to the glvalue *x is allowed in a constant expression, and so the following is well-formed:

constexpr int y = *x;

Neither gcc or clang trunk currently accept this as a constant expression, despite it being well-formed according to the standard.

Is this intended?

A full demo program:

const int x[2] = {42, 43};
constexpr int y = *x;
int main() {}

Implementations similarly fail with the equivalent lvalue x[0] as well.