enum vs constexpr для фактических статических констант внутри классов
позвольте мне начать с изложения моего намерения. В старые (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. (Unforseen по крайней мере для меня) проблема заключается в том, что это также вызывает сценарии, где 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
как перечисление (т. е. старая школа), чтобы избежать необходимости добавлять определение в файл cpp для некоторых, но не всех моих статических переменных-членов constexpr. Разве нет способа сказать компилятору, что constexpr int SOME_VALUE=27
означает, что SOME_VALUE
надо лечить только как константа времени компиляции и никогда объект с внешней связью? Если вы видите ссылку const, используемую с ней, создайте временную. Если вы видите, что его адрес взят, создайте компиляцию Ошибка времени, если это то, что нужно, потому что это постоянная времени компиляции и ничего больше.
вот некоторые, казалось бы, доброкачественный пример кода, который заставляет нас нужно добавить определение для SOME_VALUE
в файле cpp (еще раз протестирован с gcc 4.7.2):
#include <vector>
class C
{
public:
static constexpr int SOME_VALUE=5;
};
int main()
{
std::vector<int> 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;
6 ответов:
для сведения,
static constexpr
версия будет работать, как вы ожидали в C++17. Из Приложения N4618 D. 1 [depr.static_constexpr]:D. 1 повторное объявление
static constexpr
элементы данных [depr.static_constexpr]для совместимости с предыдущими международными стандартами C++, a
constexpr
статический элемент данных может быть избыточно объявлен вне класса без инициализатора. Это использование не рекомендуется. [пример: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 [класс.статический.data] / 3:
[...] Встроенный статический элемент данных может быть определен в определении класса и может указывать бандаж или равно инициализатор. Если элемент объявлен с помощью
constexpr
описатель, он может быть повторно объявлен в области видимости пространства имен без инициализатора (это использование не рекомендуется; см. D. 1). [...]это приходит с таким же машинным оборудованием которое ввело non-
constexpr
версия того же самого, встроенные статические элементы данных.struct A { static inline int n = 5; // definition (illegal in C++ 2014) }; inline int A::n; // illegal
у вас есть три варианта:
Если ваш класс является шаблоном, то поместите определение статического члена в сам заголовок. Компилятор должен идентифицировать его как одно определение только в нескольких единицах перевода (см. [basic.защита.odr] / 5)
Если ваш класс не является шаблоном вы можете легко поместить его в исходный файл
альтернативно объявить статическую функцию-член constexpr getSomeValue ():
class C { public: static constexpr int getSomeValue() { return 27; } };
Я бы пошел с перечислением класса:
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<int>(r); // OK, n = 21
из стандарта 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 ошибается.
для целей замены перечисления номер 2 должен работать нормально. Он по-прежнему имеет внутреннюю связь без
static
ключевое слово.[отредактировано в ответ на комментарий]