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

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

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

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

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

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

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

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

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

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

Вот какой-то, казалось бы, доброкачественный образец кода, который заставляет нас добавить определение для 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 obvious at compile time
}

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

constexpr int C::SOME_VALUE;

Ответ 1

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

D.1 Обновление элементов данных static constexpr [des.static_constexpr]

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

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

constexpr int A::n; // redundant declaration (definition in C++ 2014)

-end пример]

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

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

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

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

inline int A::n; // illegal

Ответ 2

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

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

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

  • Альтернативно объявлять константную функцию-член constexpr getSomeValue():

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

Ответ 3

Я бы пошел с классом 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<int>(r); // OK, n = 21

Ответ 4

Из стандарта С++ 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.

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

Ответ 5

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

enum class : int C { SOME_VALUE = 5 };

Ответ 6

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

class C
{
public:
  static const int SOME_VALUE=5;
};

int main()
{
  std::vector<int> iv;
  iv.push_back(C::SOME_VALUE); 
}

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