enum vs constexpr для реальных статических констант внутри classов

Позвольте мне начать с моего намерения. В старые времена (C ++) у нас был бы код вроде:

class C { public: enum {SOME_VALUE=27}; }; 

Затем мы могли бы использовать SOME_VALUE во всем нашем коде как постоянную времени компиляции, и везде, где компилятор увидит C::SOME_VALUE , он просто вставляет литерал 27.

Теперь, кажется, более приемлемо изменить этот код на что-то вроде:

 class C { public: static constexpr int SOME_VALUE=27; }; 

Это выглядит намного чище, дает SOME_VALUE четко определенный тип и, по-видимому, является предпочтительным подходом на C ++ 11. Проблема (непредвиденная, по крайней мере, для меня) заключается в том, что это также вызывает сценарии, в которых SOME_VALUE необходимо сделать внешним. То есть в каком-то файле cpp нам нужно добавить:

 constexpr int C::SOME_VALUE; // Now C::SOME_VALUE has external linkage 

Случаи, которые вызывают это, похоже, когда используются ссылки const на SOME_VALUE , что происходит довольно часто в C ++ стандартном библиотечном коде (см. Пример внизу этого вопроса). Кстати, я использую gcc 4.7.2 как свой компилятор.

Из-за этой дилеммы я вынужден вернуться к определению SOME_VALUE как enums (т. SOME_VALUE Старой школы), чтобы избежать необходимости добавлять определение в файл cpp для некоторых, но не для всех моих статических переменных-членов constexpr. Разве нет способа сообщить компилятору, что constexpr int SOME_VALUE=27 означает, что SOME_VALUE следует рассматривать только как константу времени компиляции и никогда не объект с внешней связью? Если вы видите ссылку на константу, используемую с ней, создайте временную. Если вы видите его адрес, создайте ошибку времени компиляции, если это то, что необходимо, потому что это постоянная времени компиляции и ничего больше.

Вот какой-то, казалось бы, доброкачественный образец кода, который заставляет нас добавить определение для SOME_VALUE в файл cpp (еще раз, проверенный с помощью gcc 4.7.2):

 #include  class C { public: static constexpr int SOME_VALUE=5; }; int main() { std::vector iv; iv.push_back(C::SOME_VALUE); // Will cause an undefined reference error // at link time, because the compiler isn't smart // enough to treat C::SOME_VALUE as the literal 5 // even though it's obvious at compile time } 

Добавление следующей строки в код в области файла приведет к ошибке:

 constexpr int C::SOME_VALUE; 

Для записи static constexpr версия static constexpr будет работать так, как вы ожидали в C ++ 17. Из N4618 Приложение D.1 [des.static_constexpr] :

D.1 Переобучение static constexpr данных constexpr [des.static_constexpr]

Для совместимости с предыдущими международными стандартами C ++ constexpr статических данных constexpr может быть избыточно переопределен вне classа без инициализатора. Это использование устарело. [ Пример:

 struct A { static constexpr int n = 5; // definition (declaration in C++ 2014) }; constexpr int A::n; // redundant declaration (definition in C++ 2014) 

конец примера ]

Соответствующий стандартный текст, который разрешает это, – N4618 9.2.3 [class.static.data] / 3 :

[…] Встроенный элемент статических данных может быть определен в определении classа и может указывать логический или равный-инициализатор . Если член объявлен с параметром constexpr , он может быть переопределен в области пространства имен без инициализатора (это использование устарело, см. D.1). […]

Это constexpr с тем же механизмом, который ввел версию constexpr того же самого, встроенные элементы статических данных .

 struct A { static inline int n = 5; // definition (illegal in C++ 2014) }; inline int A::n; // illegal 

У вас есть три варианта:

  1. Если ваш class является шаблоном, тогда установите определение статического члена в самом заголовке. Компилятор должен идентифицировать его как одно определение только для нескольких единиц перевода (см. [Basic.def.odr] / 5)

  2. Если ваш class не является шаблоном, вы можете легко поместить его в исходный файл

  3. В качестве альтернативы объявите constexpr static member function getSomeValue ():

     class C { public: static constexpr int getSomeValue() { return 27; } }; 

Я бы пошел с classом enum:

http://en.cppreference.com/w/cpp/language/enum

http://www.stroustrup.com/C++11FAQ.html#enum

Из первой ссылки:

 enum class Color { RED, GREEN=20, BLUE}; Color r = Color::BLUE; switch(r) { case Color::RED : std::cout << "red\n"; break; case Color::GREEN : std::cout << "green\n"; break; case Color::BLUE : std::cout << "blue\n"; break; } // int n = r; // error: no scoped enum to int conversion int n = static_cast(r); // OK, n = 21 

В настоящее время предпочтительным способом является:

 enum class : int C { SOME_VALUE = 5 }; 

Из стандарта C ++ N3797 S3.5 / 2-3

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

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

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

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

Имя, имеющее область пространства имен (3.3.6), имеет внутреннюю связь, если это имя

– шаблон переменной, функции или функции, который явно объявлен статическим; или,

– энергонезависимая переменная, которая явно объявлена ​​как const или constexpr, и ни одно из явно объявленных extern или ранее не объявлено, что имеет внешнюю связь; или

– член данных анонимного объединения.

Мое чтение гласит, что в следующем коде:

 public: static constexpr int SOME_VALUE=5; constexpr int SOME_VALUE=5; }; static constexpr int SOME_VALUE=5; constexpr int SOME_VALUE=5; 

Все 4 экземпляра SOME_VALUE имеют внутреннюю связь. Они должны ссылаться со ссылкой на SOME_VALUE в одной и той же единицы перевода и не быть видимыми в другом месте.

Очевидно, что первое – это декларация, а не определение. Он нуждается в определении в пределах одной единицы перевода. Если GCC говорит так, и MSVC не делает этого, то MSVC ошибается.

В целях замены enums номер 2 должен работать нормально. Он по-прежнему имеет внутреннюю связь без static ключевого слова.

[Отредактировано в ответ на комментарий]

вы можете сделать это

 class C { public: static const int SOME_VALUE=5; }; int main() { std::vector iv; iv.push_back(C::SOME_VALUE); } 

Это даже не C ++ 11, просто C ++ 98

  • Самоинициализация статической переменной constexpr, хорошо ли она сформирована?
  • constexpr не работает, если функция объявлена ​​внутри classа
  • constexpr и инициализация статического константного указателя void с реинтерпретом, который компилятор прав?
  • Разница между `constexpr` и` const`
  • Когда вы должны использовать функцию constexpr в C ++ 11?
  • C ++ concat два строковых литерала `const char`
  • перегрузка constexpr
  • const vs constexpr для переменных
  • Неопределенная ссылка на статический constexpr char
  • constexpr инициализирует статический член, используя статическую функцию
  • Ошибка компоновщика C ++ с classом static constexpr
  • Давайте будем гением компьютера.