Подтвердить что ты не робот

Является ли массив constexpr обязательно odr-используемым при индексировании?

С учетом следующего кода:

struct A { static constexpr int a[3] = {1,2,3}; };

int main () {
  int a = A::a[0];
  int b  [A::a[1]];
}

есть A::a обязательно odr-used в int a = A::a[0]?


Примечание: Этот вопрос представляет собой менее пламенную/нелогичную/бесконечную версию дискуссии в лаундже.

4b9b3361

Ответ 1

Первое использование A::a:

int a = A::a[0];

Инициализатор является константным выражением, но это не останавливает A::a от использования odr. И, действительно, A::a является odr-используемым этим выражением.

Начиная с выражения A::a[0], пройдите через [basic.def.odr] (3.2)/3 (для будущих читателей я использую формулировку из N3936):

Переменная x [в нашем случае A::a], имя которой отображается как потенциально вычисленное выражение ex [в нашем случае id-expression A::a] используется odr, если

  • Применение преобразования lvalue-to-rval в x дает постоянное выражение [оно делает] который не вызывает никаких нетривиальных функций [это не так] и

  • if x является объектом [it],

    • ex является элементом набора потенциальных результатов выражения e, где либо преобразование lvalue-to-rale применяется к e, либо e является выражением отбрасываемого значения.

Итак: какие существуют возможные значения e? Набор потенциальных результатов выражения представляет собой набор подвыражений выражения (вы можете проверить это, прочитав [basic.def.odr] (3.2)/2), поэтому нам нужно только рассмотрим выражения, которые ex являются подвыражением. Это:

A::a
A::a[0]

Из них преобразование lvalue-в-значение не применяется сразу к A::a, поэтому мы рассматриваем только A::a[0]. Per [basic.def.odr] (3.2)/2, набор потенциальных результатов A::a[0] пуст, поэтому A::a является odr-используемым этим выражением.

Теперь вы можете утверждать, что сначала переписываем A::a[0] в *(A::a + 0). Но это ничего не меняет: возможные значения e тогда

A::a
A::a + 0
(A::a + 0)
*(A::a + 0)

Из них только четвертый имеет преобразование lvalue-rvalue, применяемое к нему, и снова [basic.def.odr] (3.2)/2 говорит, что набор потенциальных результатов of *(A::a + 0) пусто. В частности, обратите внимание, что при распаде матрицы к указателю не преобразование lvalue-to-rvalue ([conv.lval] (4.1)), даже если оно преобразует array lvalue к rinterue указателя - это преобразование от массива к указателю ( [conv.array] (4.2)).

Второе использование A::a:

int b  [A::a[1]];

Это ничем не отличается от первого случая, согласно стандарту. Опять же, A::a[1] является константным выражением, поэтому это допустимая граница массива, но компилятору по-прежнему разрешено испускать код во время выполнения, чтобы вычислить это значение, а привязанный массив еще odr использует A::a.

Обратите внимание, в частности, что константные выражения являются (по умолчанию) потенциально-оцененными выражениями. Per [basic.def.odr] (3.2)/2:

Выражение потенциально оценивается, если оно не является неоцененным операндом (раздел 5) или его подвыражением.

[expr] (5)/8 просто перенаправляет нас на другие подпункты:

В некоторых контекстах отображаются неоцененные операнды (5.2.8, 5.3.3, 5.3.7, 7.1.6.2). Неопределенный операнд не оценивается.

В этих подразделах говорится, что (соответственно) операнд некоторых выражений typeid, операнд sizeof, операнд noexcept и операнд decltype являются неоцененными операндами. Других видов неориентированного операнда нет.

Ответ 2

Да, A::a используется odr.

В С++ 11 соответствующая формулировка - 3.2p2 [basic.def.odr]:

[...] Переменная, имя которой отображается как потенциально оцениваемое выражение, используется odr, если это не объект, который удовлетворяет требованиям для отображения в постоянном выражении (5.19) и преобразовании lvalue-to-rvalue ( 4.1) немедленно применяется. [...]

Имя переменной A::a появляется в объявлении int a = A::a[0], в полном выражении A::a[0], которое является потенциально оцененным выражением. A::a:

  • объект
  • который удовлетворяет требованиям для отображения в постоянном выражении

Однако преобразование lvalue-rvalue не применяется сразу к A::a; он применяется к выражению A::a[0]. В самом деле, преобразование lvalue-to-rvalue может не применяться к объекту типа массива (4.1p1).

Итак, A::a используется odr.


Так как С++ 11, правила несколько расширились. DR712 Являются ли целочисленные константные операнды условного выражения "used?" , представляет концепцию набора потенциальных результатов выражения, которое позволяет выражениям, таким как x ? S::a : S::b, избегать использования odr. Однако, хотя набор потенциальных результатов относится к таким операторам, как оператор условного оператора и запятой, он не учитывает индексацию или косвенность; поэтому A::a по-прежнему используется odr в текущих черновиках для С++ 14 (n3936 по дате).

[Я считаю, что это сжатый эквивалент ответа Ричарда Смита, который, однако, не упоминает об изменении с С++ 11.]

В Когда переменная odr, используемая в С++ 14?, мы обсуждаем эту проблему и возможные изменения формулировок в разделе 3.2, чтобы разрешить индексирование или опосредование массива избегайте использования odr.

Ответ 3

Нет, это не используется odr.

Сначала ваш массив и его элементы имеют литеральный тип:

[C++11: 3.9/10]: Тип - это буквальный тип, если он:

  • скалярный тип; или
  • тип класса (раздел 9) с
  • тривиальный конструктор копирования,
  • нет нетривиального конструктора перемещения,
  • тривиальный деструктор,
  • тривиальный конструктор по умолчанию или хотя бы один конструктор constexpr, отличный от конструктора копирования или перемещения, и
  • все нестатические элементы данных и базовые классы литералов; или
  • массив литералов типа.

Теперь мы рассмотрим правила, используемые odr:

[C++11: 3.2/2]: [..] Переменная или неперегруженная функция, имя которой отображается как потенциально оцениваемое выражение, используется odr, если это не объект, который удовлетворяет требованиям для отображения в постоянном выражении (5.19) и Преобразование lvalue-to-rvalue (4.1) немедленно применяется. [..]

И здесь мы ссылаемся на правила постоянных выражений, которые не содержат ничего, что запрещает вашему инициализатору быть постоянным выражением; соответствующие отрывки:

[C++11: 5.19/2]: Условное выражение является константным выражением, если оно не включает одно из следующих значений в качестве потенциально оцениваемого подвыражения [..]:

  • [..]
  • преобразование lvalue-to-rvalue (4.1), если оно не применяется к
    • значение целочисленного или перечисляемого типа, которое относится к энергонезависимому объекту const с предшествующей инициализацией, инициализированным константным выражением, или
    • glvalue типа literal, который относится к энергонезависимому объекту, определенному с помощью constexpr, или который относится к под-объекту такого объекта или
    • glvalue типа literal, который ссылается на энергонезависимый временный объект, инициализированный константным выражением;
  • [..]

(Не откладывайте имя производства, "условное выражение": это единственное производство постоянного выражения и, следовательно, тот, который мы ищем.)

Затем, думая об эквивалентности от A::a[0] до *(A::a + 0), после преобразования от массива к указателю вы имеете значение rvalue:

[C++11: 4.2/1]: Значение lvalue или rvalue массива типа N T "или" массив неизвестной границы T "может быть преобразовано в prvalue типа" указатель на T ". Результатом является указатель на первый элемент массива.

Затем выполняется арифметика указателя на этом значении, и результат также является значением r, используемым для инициализации a. Здесь нет преобразования lvalue-rvalue, поэтому ничего не нарушается "требования к появлению в постоянном выражении".