Ошибка компоновщика C ++ с классом static constexpr - PullRequest
44 голосов
/ 10 декабря 2011

Я компилирую следующую простую программу с 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

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

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

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

Ответы [ 6 ]

32 голосов
/ 10 декабря 2011

Я не думаю, что это ошибка. Если вы измените значение 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 может оцениваться во время компиляции, но не требуется для оценки как таковой, и в равной степени может происходить во время выполнения. "компилятору не следует всегда обрабатывать символ как выражение" , он намекает на то, что было бы разумным и допустимым сделать это, если бы компилятор захотел этого.

12 голосов
/ 17 апреля 2016

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

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

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

6 голосов
/ 17 ноября 2018

Это было исправлено в C ++ 17.

https://en.cppreference.com/w/cpp/language/static:

Если статический элемент данных объявлен constexpr, он неявно встроен и не нуждается в переобъявлении в области пространства имен. это переопределение без инициализатора (ранее требовалось, как показано выше) все еще разрешено, но не рекомендуется.

5 голосов
/ 04 июля 2018

Вам также необходимо предоставить определение для члена constexpr вне структуры (или класса), но на этот раз без его значения.Смотрите здесь: https://en.cppreference.com/w/cpp/language/static

#include <algorithm>

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

constexpr int S::X;

int main()
{
    return std::min(S::X, 0);
};
4 голосов
/ 24 июня 2014

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

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

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

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

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

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

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

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

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

1 голос
/ 20 июня 2014

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

...