Ошибка компоновщика С++ с классом static constexpr

Я компилирую следующую простую программу с g++-4.6.1 --std=c++0x:

#include <algorithm>

struct S
{
    static constexpr int X = 10;
};

int main()
{
    return std::min(S::X, 0);
};

Я получаю следующую ошибку компоновщика:

/tmp/ccBj7UBt.o: In function `main':
scratch.cpp:(.text+0x17): undefined reference to `S::X'
collect2: ld returned 1 exit status

Я понимаю, что встроенные в static статические члены не имеют определенных символов, но я находился под (вероятно, ошибочным) впечатлением, что использование constexpr подсказывало компилятору всегда относиться к символу как к выражению; поэтому компилятор знал бы, что не имеет права передавать ссылку на символ S::X (по той же причине вы не можете взять ссылку на литерал 10).

Однако, если S объявлено как пространство имен, то есть "пространство имен S" вместо "struct S", все ссылки отлично.

Является ли это ошибкой g++ или мне все еще нужно использовать трюк, чтобы обойти это раздражение?

Ответ 1

Я не думаю, что это ошибка. Если вы измените constexpr на const, он все равно будет терпеть неудачу с той же ошибкой.

Вы объявили S::X, но не определили его нигде, поэтому для него нет хранилища. Если вы делаете что-либо с этим, что нужно знать адрес его, вам также нужно определить его где-то еще.

Примеры:

int main() {
      int i = S::X; // fine
      foo<S::X>(); // fine
      const int *p = &S::X; // needs definition
      return std::min(S::X, 0); // needs it also
}

Причиной этого является то, что constexpr может быть оценен во время компиляции, но не требуется его оценивать как таковое и может также выполняться во время выполнения. Он не инструктирует "компилятор всегда относиться к символу как к выражению", он намекает, что было бы разумно и допустимо сделать это, если бы компилятор почувствовал это.

Ответ 2

Причина ошибки уже объяснена, поэтому я просто добавлю обходной путь.

return std::min(int(S::X), 0);

Это создает временную, поэтому std::min может ссылаться на нее.

Ответ 3

В стандарте С++ (последний рабочий проект):

Имя, имеющее область пространства имен (3.3.6), имеет внутреннюю привязку, если это имя [...] переменной, явно объявленной const или constexpr, и ни одно явно объявленное extern и ранее не объявленное иметь внешнюю связь [...].

"Связывание" определяется следующим образом:

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

- Если имя имеет внешнюю привязку, объект, который он обозначает, может ссылаться на имена из областей другие единицы перевода или из других областей той же единицы перевода.

- Если имя имеет внутреннюю связь, обозначаемый объект может ссылаться на имена из других областей в той же единице перевода.

- Если имя не имеет привязки, объект, который он обозначает, не может ссылаться на имена из других областей.

Таким образом, в случае namespace S он будет иметь внешнюю связь, в случае struct S он будет иметь внутреннюю связь.

Символы с внешней связью должны иметь явно определенный символ в некоторой единицы перевода.

Ответ 4

Ваше понимание constexpr неверно. Объявлено значение lvalue constexpr все еще является lvalue, и объявленная функция constexpr по-прежнему является функцией. И когда функция имеет ссылочный параметр, и он передается lvalue, язык требует, чтобы ссылка ссылалась на эту lvalue, и ничего остальное. (При применении к переменной типа int существует очень мало различий между constexpr и равным const.)