Странное поведение с константной переменной 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
должен быть определен.