Difference between const and constexpr arrays

2019-04-21 04:14发布

问题:

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.

回答1:

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.



回答2:

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.