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 60

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

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

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

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

  3. альтернативно объявить статическую функцию-член 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

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

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 ошибается.

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

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

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

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

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

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