Странное поведение с константной переменной constexpr

Это следующий вопрос к Undefined ссылке на статический constexpr char [] [].

Следующая программа строит и работает нормально.

#include <iostream>

struct A {
   constexpr static char dict[] = "test";

   void print() {
      std::cout << A::dict[0] << std::endl;
   }
};

int main() {
   A a;
   a.print();
   return 0;
}

Однако, если я изменяю A::print() на:

   void print() {
      std::cout << A::dict << std::endl;
   }

Я получаю следующую ошибку компоновщика в g++ 4.8.2.

/tmp/cczmF84A.o: In function `A::print()':
socc.cc:(.text._ZN1A5printEv[_ZN1A5printEv]+0xd): undefined reference to `A::dict'
collect2: error: ld returned 1 exit status

Ошибка компоновщика может быть решена путем добавления строки:

constexpr char A::dict[];

вне определения класса.

Однако мне непонятно, почему использование одного из членов массива не вызывает ошибки компоновщика при использовании массива, вызывающего ошибку компоновщика.

Ответ 1

Стандарт не требует никакой диагностики для отказа предоставить определение, где требуется.

3.2 Одно правило определения [basic.def.odr]

4 Каждая программа должна содержать ровно одно определение каждой не-встроенной функции или переменной, которая является odr-используемой в этой программе; не требуется диагностика. [...]

Это означает, что реализациям разрешено оптимизировать доступ к таким переменным и что происходит в вашем первом случае с GCC.

Как GCC, так и clang решили, что они предпочитают постоянный пользовательский интерфейс, где сообщения об ошибках в отношении отсутствующих определений не зависят от уровня оптимизации. Обычно это означает, что любое отсутствующее определение вызывает сообщение об ошибке. Однако в этом случае GCC делает минимальную оптимизацию даже при -O0, избегая ошибки.

Но программа является ошибкой в ​​любом случае, потому что даже A::dict[0] является ODR-использованием:

3.2 Одно правило определения [basic.def.odr]

3 Переменная x, имя которой отображается как потенциально оцененное выражение ex, является odr-используемым ex, если применение преобразования lvalue-to-rvalue (4.1) к x не дает постоянного выражения ( 5.19), который не вызывает никаких нетривиальных функций и, если x является объектом, ex является элементом множества потенциальных результатов выражения e, где либо преобразование lvalue-to-rvalue ( 4.1) применяется к e, или e - выражение с отброшенными значениями (п. 5). [...]

Использование A::dict не включает преобразование lvalue-to-rvalue, оно включает преобразование между массивами и указателями, поэтому исключение не применяется.

Ответ 2

В дополнение к информации, предоставленной @hvd в его ответе...

Из проекта С++ Draft N3337 (выделено мной):

9.4.2 Элементы статических данных

3 Если элемент данных с энергонезависимой структурой const static имеет тип интеграла или перечисления, его объявление в определении класса может указывать логический или равный-инициализатор, в котором каждое предложение-инициализатор, являющееся выражением присваивания, постоянное выражение (5.19). A static член данных типа literal может быть объявлен в определении класса с помощью спецификатора constexpr; если это так, в его декларации указывается логический или равный-инициализатор, в котором каждое предложение-инициализатор, являющееся выражением-присваиванием, является постоянным выражением. [Примечание. В обоих случаях член может отображаться в постоянных выражениях. - end note] Элемент все еще должен быть определен в области пространства имен, если он используется в odr (3.2) в программе, а определение области пространства имен не должно содержать инициализатор.

Учитывая, что A::data является odr-используемым в выражении A::data[0], в соответствии со стандартом, он должен быть определен в области пространства имен. Тот факт, что g++ способен успешно создать программу без A::data, которая определена в области пространства имен, не делает программу правильной. Чтобы соответствовать стандартам, A::data должен быть определен.