Ошибка компоновщика С++ с классом 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
.)