19

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?

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
Andrew Tomazos
  • 66,139
  • 40
  • 186
  • 319
  • That seems to be what this question yesterday was about: http://stackoverflow.com/questions/18878427 – us2012 Sep 19 '13 at 19:21
  • 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 Sep 19 '13 at 19:24
  • 7
    Guys, it isn't a dupe! The answer doesn't apply here! This looks like a compiler bug or standard defect. – Andrew Tomazos Sep 19 '13 at 19:25
  • Retracted the dupe vote. (Good thing you can do that now!) – us2012 Sep 19 '13 at 19:39
  • What happens if you put `constexpr` on the declaration of `x[]`? – StilesCrisis Sep 19 '13 at 19:44
  • @StilesCrisis: It works with constexpr, but that's not relevant - it looks like `const int x[]` should work too according to the standard, but it doesn't. – Andrew Tomazos Sep 19 '13 at 19:50
  • 3
    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 Sep 19 '13 at 19:54
  • @StilesCrisis: The non-normative note next to the first bullet point (that I quoted in the OP) in a recent draft (N3485) says "a string literal (2.14.5) corresponds to an array of such objects." - so it is clear that it meant to be applicable to at least some subobjects of arrays. – Andrew Tomazos Sep 19 '13 at 19:59

2 Answers2

11

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.

Casey
  • 41,449
  • 7
  • 95
  • 125
rodrigo
  • 94,151
  • 12
  • 143
  • 190
  • How does the first bullet point not apply? In `const int x[] = {1,2,3}` the `x[0]` object is initialized with `1` isn't it? – Andrew Tomazos Sep 19 '13 at 19:55
  • 6
    @user1131467: No, `x[0]` is not initialized to anything. `x` is initialized to `{1,2,3}`, so `x[0]` ends up having a value of `1`. – rodrigo Sep 19 '13 at 19:58
  • I don't think that is correct. The `x[0]` object is not uninitialized, it is initialized with the prvalue `1`. I'll check the rules about array initialization. – Andrew Tomazos Sep 19 '13 at 20:03
  • 3
    8.5.1p2 "When an [array] is initialized by an initializer list the elements of the initializer list are taken as initializers for the members of the [array], in increasing subscript or member order." So in `const int x[] = {1,2,3}`, `1` is the initializer of the object `x[0]` - sorry, but `x[0]` is clearly initialized. – Andrew Tomazos Sep 19 '13 at 20:06
  • @user1131467: Granted, the first element of the array is initialized with the `1`, but that element is unnamed. The `x[0]` is a l-value expression that happens to refer to that very same element. Or would you also say that `*x` is initialized with `1`? Because remember that `x[0]` and `*x` are synonyms. – rodrigo Sep 19 '13 at 20:40
  • 4
    @user1131467: Careful examination of the second bullet shows why the first doesn't apply... `x[0]` is clearly a *subobject*, not a complete object, and subobjects are permitted in bullet #2 but not bullet #1. – Ben Voigt Sep 19 '13 at 20:43
  • @user1131467: Standardese aside, the point is that to be considered a constant expression, something must be marked as `constexpr`. `const` only means "you cannot modify me here". It's simply a convenient thing that a single const object initialized with a constant expression is implicitly `constexpr`, as in `const x = 1`. This is possible because the initializer is readily available and solely determines if the object can be a constant expression. For an array, every initializer has to be checked, and the results can be mixed. Easier to default to "not `constexpr`, but you can tell me to be". – GManNickG Sep 19 '13 at 23:13
  • @rodrigo: The requirement is about a glvalue referring to an object. So yes `*x`, `x[0]`, `*(x+2-1-1)` are all glvalues and all refer to objects. Provided the expression is also a core constant expression the requirement is available. – Andrew Tomazos Sep 20 '13 at 07:47
  • @BenVoigt: A worthy try :), but a sub-object is an object. The requirement doesn't consider only complete objects, but all objects. The reason sub-objects are not mentioned in the first bullet point is that objects of integral or enumeration type do not have sub-objects. See the normative note next to the first bullet point in N3485 for clarification. – Andrew Tomazos Sep 20 '13 at 07:50
  • "Your example does include lvalue-to-rvalue conversion" interestingly, the lvalue-to-rvalue conversion applies to the result of `*(x+0)` (which is the definition of `x[0]`). `x` itself is converted to an rvalue via the array-to-pointer conversion, therefore the lvalue-to-rvalue restrictions don't apply to `x` directly here. – dyp Sep 20 '13 at 11:50
  • @user1131467: If *object* includes subobjects, it would not be necessary for bullet point number 2 to mention subobjects explicitly. The wording of bullet point number 2 shows that in this paragraph, the word *object* is used to only mean complete objects. Objects of integral type don't have subobjects, but they can *be* subobjects. The `const` object you have, which is `x`, is an aggregate, not an object of integral type. – Ben Voigt Sep 20 '13 at 13:31
  • @user1131467: I think that the intention of the standard is to specify what both GCC and CLang do. Otherwise, it might be impossible to implement. Maybe in the first bullet, they should say: _non-volatile const *complete* object_. – rodrigo Sep 20 '13 at 14:13
  • @BenVoigt: Actually _objects_ do include _subobjects_; _complete objects_ do not. Bullet 2 refer to the subobjects because subobjects cannot be defined with `constexpr`. – rodrigo Sep 20 '13 at 14:21
  • @BenVoigt (@discussion) `static_assert("hello"[0] == 'h', "!");` is accepted by both clang++ and g++. In fact, some time after the Standard has been published, the committee added a note to the first sub-bullet "a string literal corresponds to an array of such objects". A string literal is an lvalue [sic!], and I don't think it's a temporary (it has static storage duration as well), so the third sub-bullet doesn't apply. (Again: arrays are *not* converted to rvalues via the lvalue-to-rvalue conversion (4.1), but via the array-to-pointer conversion.) – dyp Sep 20 '13 at 15:46
  • @rodrigo: Wait what? You're saying that `constexpr int a[2] = { 1, 2 };` doesn't define a complete object of aggregate type `int[2]` and two subobjects of primitive type `int`? – Ben Voigt Sep 20 '13 at 20:17
  • @BenVoigt: Sorry, I'm not a native English speaker. Of course, subobjects _are_ a part of complete objects. What I meant is that subobjects _are not_complete objects. That is, in your example `constexpr int a[2] = { 1, 2 };` the `a[1]` element is not a complete object, and so it is not properly defined with `constexpr`. – rodrigo Sep 20 '13 at 21:47
  • @rodrigo: If `a[1]` is not defined by `constexpr int a[2] = { 1, 2 };`, then what is it defined by, or is it not defined at all? If it IS defined by `constexpr int a[2] = { 1, 2 };`, then surely one would say it is defined with `constexpr`, because its definition uses `constexpr`, no? – Ben Voigt Sep 20 '13 at 22:02
  • @BenVoigt: In `constexpr int a[2] = {1, 2};`, `a` is defined with constexpr. Neither `a[0]` nor `a[1]` are defined with constexpr. If the second bullet point didn't specifically include subobjects, then it would not apply to `a[0]`. As it happens the first bullet point applies anyway because `constexpr int a[]` declares a `const array of int` which is adjusted to `array of const int`, so the `a[0]` object is const int. But in a case like `constexpr double b[2] = {1.0, 2.0}` it would not, and `b[0]` would not be a constant expression if lvalue-to-rvalue conversion was applied. Clear as mud? – Andrew Tomazos Sep 21 '13 at 01:13
  • @Andrew: All except the part where you said that `a[0]` is not defined with `constexpr`. If you argued that it isn't `constexpr`-qualified, I could agree. But it is necessarily defined with the line of code `constexpr int a[2] = { 1, 2 };`, because there's nothing else defining it. – Ben Voigt Sep 21 '13 at 01:18
  • @BenVoigt: For the array case you could argue it either way I suppose, but for a class type the subobjects have their own definitions in the class specifier. Recall that `constexpr` is a specifier and as such it is applied to declarations, of which definitions are a subset. I suppose to make it clear they explicitly say "including subobjects" to remove any doubt as to whether the `constexpr` specifier transfers the semantic to subobjects. I wouldn't read too much into it. – Andrew Tomazos Sep 21 '13 at 01:48
4

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.

Andrew Tomazos
  • 66,139
  • 40
  • 186
  • 319
  • 2
    I'll give you an upvote if you don't just link to that explanation but also put it here ;) – dyp Sep 20 '13 at 11:55
  • It might be intended by the implementations btw, consider `const int x[2] = {42, calculate_something()};` where `calculate_something()` is not a constant expression. They had to keep track of individual elements of arrays. – dyp Sep 20 '13 at 12:03
  • @DyP: Correct, the compiler has to keep track whether individual subobjects are initialized with constant expressions in order to implement the current requirements. So in your example `x[0]` as a prvalue is a constant expression, while `x[1]` isn't. Same work as `const int x0 = 42; const int x1 = calculate_something();`. – Andrew Tomazos Sep 20 '13 at 12:18
  • 3
    You're quoting that as if it were a reliable source... it is not. – Ben Voigt Sep 20 '13 at 13:34
  • @BenVoigt: The analysis is correct, and the DR was confirmed by Richard Smith. The current standard does clearly consider the program well-formed. – Andrew Tomazos Sep 20 '13 at 19:56
  • You have finally decided to change your SO username? After 15k rep.. Well, hello then Andrew ;) – dyp Sep 20 '13 at 20:04
  • @DyP: haha, I'm extremely lazy. – Andrew Tomazos Sep 20 '13 at 20:06
  • 1
    @AndrewTomazos: The only confirmation you got is that my interpretation is correct but the current wording should be improved. Explicitly saying "complete objects" is undoubtedly better than relying on contrast in parallel construction. – Ben Voigt Sep 20 '13 at 20:45
  • @BenVoigt: Your interpretation was incorrect, a subobject is an object - as shown by the string literal note. If the intention was a complete object, string literals wouldn't work. – Andrew Tomazos Sep 20 '13 at 23:58
  • 1
    @Andrew: If I write "The dress code permits blue and navy long-sleeve shirts. Short-sleeve blue shirts may be worn on Fridays." we would conclude that short-sleeve navy shirts are not permitted. Blue would ordinarily include navy shades, but because of the parallel contrasting construction it is clear that in this usage blue and navy are mutually exclusive. Wouldn't you agree? How is this situation different? They meant *complete objects*, compiler writers implemented the rule as *complete objects*. Yes the wording should be updated for clarity. – Ben Voigt Sep 21 '13 at 00:06
  • String literals are a unique case in that, unlike the case being discussed, they don't possess identity. Defining elements of string literals as complete objects might be an approach to deal with that ambiguity. Otherwise an entire string literal could not contain a complete object at all. – Ben Voigt Sep 21 '13 at 00:08
  • @BenVoigt: The first bullet point in the current standard considers all objects, complete and sub. String literals are arrays. An ordinary string literal is an lvalue referring to a complete object of type `array of N const char`, each character including terminating null is a subobject of type `const char`. `"foo"[1]` is an lvalue refering to an object of type `const char`, that is a subobject of the array. Applying lvalue-to-rvalue conversion to `"foo"[1]` does not disqualify the expression as a constant expression as per the first bullet point. – Andrew Tomazos Sep 21 '13 at 01:33
  • 1
    @Andrew: String literals are not so simple. Complete objects cannot overlap, while string literals can (for example `("recent" + 2) == "cent"` is perfectly possible). Which suggests that string literals are actually sequences of character objects, with each character object being a complete object, not a subobject. – Ben Voigt Sep 21 '13 at 01:36
  • @BenVoigt: Standard reference for "complete objects cannot overlap" ? – Andrew Tomazos Sep 21 '13 at 01:54
  • @Andrew: 1.8/3 says that there is a unique complete object for every subobject. I suppose that it's possible that when `("recent" + 2) == "cent"`, the string literal `"cent"` is a subobject of the string literal `"recent"`. Or that the literals aren't themselves objects, and every character which is part of the literal is a distinct complete object. – Ben Voigt Sep 21 '13 at 02:04
  • @BenVoigt: It also says in 2.14.5/12 "Whether all string literals are distinct (that is, are stored in nonoverlapping objects) is implementation-defined.", implying that overlapping objects are possible - which I guess is technically a defect contradicting the memory model - but I don't think this issue has bearing on the OP. Certainly redefining string literals from an lvalue array to "sequences" of complete objects will break more things than it would fix. Easier would be to add a forth bullet point specifically for string literals if Richards proposed resolution ends up working. – Andrew Tomazos Sep 21 '13 at 02:49
  • 1
    @Andrew: Subobjects always overlap their complete object. But complete objects can't overlap each other. – Ben Voigt Sep 21 '13 at 02:52
  • @BenVoigt: Yes, but I would say that it is currently defined by the standard that each string literal is a complete object of type array - hence the contradiction that they may overlap each other and their subobjects may not have a unique complete object. Given they have static storage duration and are immutable, this defect isn't really important. – Andrew Tomazos Sep 21 '13 at 03:10