Является ли массив 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]
?
Примечание: Этот вопрос представляет собой менее пламенную/нелогичную/бесконечную версию дискуссии в лаундже.
Ответы
Ответ 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, поэтому ничего не нарушается "требования к появлению в постоянном выражении".