10

Why is there a difference between const and constexpr when used with arrays?

int const xs[]{1, 2, 3};
constexpr int ys[]{1, 2, 3};

int as[xs[0]];  // error.
int bs[ys[0]];  // fine.

I would expect both xs[0] and ys[0] to be constant expressions but only the latter is treated as such.

5
  • constexpr values can be evaluated at compile time, const cannot. Commented Aug 9, 2013 at 11:24
  • 3
    A const array doesn't need to have a visible definition (it could be extern or could be defined later in the file), that's why there's a difference. As for why there's this difference, I suspect the standard could have said that when a definition is present and is valid for constexpr (as in your code), then const is as good as constexpr, since in that case the information is available to evaluate it. I don't know why it doesn't say that, but as good a reason as any is, "if you want your array to be constexpr then just say so". Commented Aug 9, 2013 at 11:25
  • A followup that would likely be additive to you question, I think, is why int const x = 5; and constexpr int y = 5 can both be used as fixed-size magnitudes of array decls (int a[x], b[y]), but inside arrays that are treated differently (which goes to Steve's comment above). Commented Aug 9, 2013 at 11:26
  • @SteveJessop: It would still be a little more complicated. A constexpr needs to be initialized with other constexpr, a const array can be initialized with a non-constexpr, say a function f() and const T array[] = { f(1), f(2), f(3) }; would be dynamic initialization, rather than static (constexpr) initialization. Commented Aug 9, 2013 at 12:05
  • @DavidRodríguez-dribeas: that's what "definition is present and is valid for constexpr" was intended to mean. Like you say, the definition would need to be capable of evaluation as a constexpr in order for special treatment to kick in. Commented Aug 10, 2013 at 13:01

2 Answers 2

5

A longer comment as community wiki.


The expression xs[0] is defined in [expr.sub]/1 as *((xs)+(0)). (See below for the parantheses.)

One of the expressions shall have the type “pointer to T” and the other shall have unscoped enumeration or integral type.

Therefore, the array-to-pointer conversion [conv.array] is applied:

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.

Note it can operate on an lvalue and the result is a prvalue, 0 as an integer literal is a prvalue as well. The addition is defined in [expr.add]/5. As both are prvalues, no lvalue-to-rvalue conversion is required.

int arr[3];
constexpr int* p = arr;  // allowed and compiles

The crucial step now seems to be the indirection * [expr.unary.op]/1

The unary * operator performs indirection: the expression to which it is applied shall be a pointer to an object type, or a pointer to a function type and the result is an lvalue referring to the object or function to which the expression points.

So, the result of xs[0] is an lvalue referring to the first element of the xs array, and is of type int const.


N.B. [expr.prim.general]/6

A parenthesized expression is a primary expression whose type and value are identical to those of the enclosed expression. The presence of parentheses does not affect whether the expression is an lvalue.


If we now look at the bullets in [expr.const]/2 which disallow certain expressions and conversions to appear in constant expressions, the only bullet that could apply (AFAIK) is the lvalue-to-rvalue conversion:

  • 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 [Note: a string literal (2.14.5) corresponds to an array of such objects. —end note ], or

    • a non-volatile 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

[...]

But the only true lvalue-to-rvalue conversion as per (4.1) (not 4.2, which is array-to-pointer) that appears in the evaluation of xs[0] is the conversion from the resulting lvalue referring to the first element.

For the example in the OP:

int const xs[]{1, 2, 3};
int as[xs[0]];  // error.

This element xs[0] has non-volatile const integral type, its initialization precedes the constant expression where it occurs, and it has been initialized with a constant expression.


By the way, the added "Note" in the quoted passage of [expr.const]/2 has been added to clarify that this is legal:

constexpr char c = "hello"[0];

Note that a string literal is an lvalue as well.


It would be great if someone (could change this to) explain why xs[0] is not allowed to appear in a constant expression.

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

4 Comments

The reason that the implementations rejects this is because they do not believe that this is the intent of the paragraphs (the intent was to be backwards compatible with C++03, and C++03 did not allow const int a = 0; int x[a];). C++03 did also not allow "foo"[0], but that this paragraph also allows that one was a later "let us stay make non-normative edits" change to the draft, because it was deemed desirable to read from string literals. In effect, the actual intent of bullet1 appears to be "allow reading from string literal, and stay backwards compatible with C++03".
(the above comment is based on an earlier talk with Richard Smith, who implemented constexpr evaluation for clang).
@JohannesSchaub-litb I'm not sure I fully understand your explanation. const int a = 1; int x[a]; is allowed in C++03 for all I know, and zero-sized arrays are forbidden in both, C++03 and C++11. Additionally, how does relaxing the constraints on const variables to appear in constant expressions break backwards-compatibility? Or does it mean the intent was not to change anything for const variables?
yes, that was two mistakes at once. The above was meant to be "and C++03 did allow const int a = 1; int x[a];". Not allowing this in C++11 would break backwards compatibility.
0

C++11 constexpr is used to enable an expression be evaluated at compile time, unlike const keyword.

constexpr int ys[]{1, 2, 3}; is evaluated at compile time, so no error

when ys[0] is used.

Also, notice C++11 uniform initialization is used here with {}

Other example :

constexpr int multipletwo(int x)
{
return 2*x;
}

int num_array[multipletwo(3)]; //No error since C++11, num_array has 6 elements.

Comments

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.