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.

Simple
  • 13,992
  • 2
  • 47
  • 47
  • `constexpr` values can be evaluated at compile time, `const` cannot. – teh internets is made of catz Aug 09 '13 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". – Steve Jessop Aug 09 '13 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). – WhozCraig Aug 09 '13 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. – David Rodríguez - dribeas Aug 09 '13 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. – Steve Jessop Aug 10 '13 at 13:01

2 Answers2

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.

Community
  • 1
  • 1
dyp
  • 38,334
  • 13
  • 112
  • 177
  • 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". – Johannes Schaub - litb Aug 10 '13 at 10:51
  • (the above comment is based on an earlier talk with Richard Smith, who implemented constexpr evaluation for clang). – Johannes Schaub - litb Aug 10 '13 at 10:53
  • @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? – dyp Aug 10 '13 at 13:28
  • 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. – Johannes Schaub - litb Aug 10 '13 at 14:03
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.
P0W
  • 46,614
  • 9
  • 72
  • 119