Разница между массивами const и constexpr

Почему существует разница между const и constexpr при использовании с массивами?

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

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

Я ожидал бы, что как xs[0], так и ys[0] будут постоянными выражениями, но только последний рассматривается как таковой.

Ответ 1

Более длинный комментарий как вики сообщества.


Выражение xs[0] определено в [expr.sub]/1 как *((xs)+(0)). (См. Ниже скобки).

Одно из выражений должно иметь тип "указатель на T", а другой должен иметь незапечатанную нумерацию или интегральный тип.

Следовательно, применяется преобразование от массива к указателю [conv.array]:

Значение lvalue или rvalue типа "array of N T" или "массив неизвестной границы T" может быть преобразовано в prvalue типа "указатель на T". Результатом является указатель на первый элемент массива.

Обратите внимание, что он может работать с lvalue и результатом является prvalue, 0 как целочисленный литерал также является значением prvalue. Добавление определено в [expr.add]/5. Поскольку оба являются prvalues, преобразование lvalue-rvalue не требуется.

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

Ключевым шагом теперь является косвенность * [expr.unary.op]/1

Унарный оператор * выполняет косвенное обращение: выражение, к которому оно применяется, должно быть указателем на тип объекта или указателем на тип функции, а результатом является значение l, относящееся к объекту или функции, выражения.

Итак, результат xs[0] - это lvalue, относящийся к первому элементу массива xs, и имеет тип int const.


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

Выражение в скобках - это основное выражение, тип и значение которого идентичны типу заключенного выражения. Наличие скобок не влияет на то, является ли выражение lvalue.


Если теперь посмотреть на маркеры в [expr.const]/2, которые запрещают отображение определенных выражений и преобразований в постоянных выражениях, единственной применимой маркой (AFAIK) является преобразование lvalue-to-rvalue:

  • преобразование lvalue-to-rvalue (4.1), если оно не применяется к

    • нестабильный glvalue интегрального или перечисляемого типа, который ссылается на энергонезависимый объект const с предшествующей инициализацией, инициализированный константным выражением [Примечание: строковый литерал (2.14.5) соответствует массиву таких объектов. -end note] или

    • нестабильный glvalue типа literal, который ссылается на энергонезависимый объект, определенный с помощью constexpr, или который ссылается на под-объект такого объекта, или

[...]

Но единственное истинное преобразование lvalue-to-rvalue в соответствии с (4.1) (не 4.2, которое представляет собой массив-указатель), которое появляется при оценке xs[0], - это преобразование из полученной lvalue, относящейся к первому элемент.

Для примера в OP:

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

Этот элемент xs[0] имеет нелетучий const-интегральный тип, его инициализация предшествует выражению константы там, где оно встречается, и оно было инициализировано постоянным выражением.


Кстати, добавленная "Заметка" в цитированном отрывке [expr.const]/2 была добавлена, чтобы уточнить, что это законно:

constexpr char c = "hello"[0];

Обратите внимание, что строковый литерал также является lvalue.


Было бы здорово, если бы кто-то (может изменить это) объяснил, почему xs[0] не может появляться в постоянном выражении.

Ответ 2

С++ 11 constexpr используется для включения выражения во время компиляции, в отличие от ключевого слова const.

constexpr int ys[]{1, 2, 3}; оценивается во время компиляции, поэтому ошибка

когда используется ys[0].

Кроме того, здесь используется стандартная инициализация С++ 11 с {}

Другой пример:

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

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