Правильная инициализация статического массива constexpr в шаблоне класса?

Статические члены класса в C++ вызвали у меня небольшую путаницу из-за словесности стандарта:

9.4.2 Статические члены данных [class.static.data]

Объявление статического члена данных в его определении класса не является определением...

Однако constexpr требуется инициализировать (AFAIK, не удалось найти цитату из стандарта) при его объявлении (например, в определении класса).

Из-за ограничений на constexpr я на самом деле забыл о требовании определения статических членов вне класса, пока не попытался получить доступ к статическому массиву constexpr. Этот связанный вопрос обеспечивает правильный способ определения члена массива, но меня интересует влияние этого определения на шаблон класса.

Вот чем я закончил:

template<typename T>
class MyClass
{
private:
  static constexpr std::size_t _lut[256] = { /* ... */ };
  T _data;

public:
  static constexpr std::size_t GetValue(std::size_t n) noexcept
  {
    return _lut[n & 255];
  }

  // ...
};

template<typename T>
constexpr std::size_t MyClass<T>::_lut[256];

Это правильный синтаксис? В частности, использование шаблона в определении кажется неудобным, но GCC, похоже, связывает все соответствующим образом.

В качестве дополнительного вопроса, должны ли статические члены constexpr, не являющиеся массивами, определяться аналогичным образом (с определением шаблона вне класса)?

2 ответа

Решение

Я думаю, что вы хотите 9.4.2p3:

Если энергонезависимый const static элемент данных имеет целочисленный тип или тип перечисления, его объявление в определении класса может указывать инициализатор скобок или равных, в котором каждое предложение инициализатора, являющееся выражением присваивания, является константным выражением (5.19). Статический член данных литерального типа может быть объявлен в определении класса с помощью constexpr спецификатор; если это так, его объявление должно указывать инициализатор скобок или равных, в котором каждое предложение инициализатора, являющееся выражением присваивания, является константным выражением. [...] Член по-прежнему должен быть определен в области пространства имен, если он используется в программе odr (3.2), а определение области действия пространства имен не должно содержать инициализатор.

Определение элемента статических данных шаблона - это объявление шаблона (14p1). Пример, приведенный в 14.5.1.3p1:

template<class T> class X {
  static T s;
};
template<class T> T X<T>::s = 0;

Однако, как указано выше constexpr static или же const static член, в объявлении класса которого указан инициализатор, не должен иметь инициализатор в определении области своего пространства имен, поэтому синтаксис становится следующим:

template<class T> class X {
  const static T s = 0;
};
template<class T> T X<T>::s;

Отличие от статического члена данных constexpr, не являющегося массивом (т. Е. Целочисленным или перечисляемым), состоит в том, что его использование в преобразовании lvalue-to-rvalue не является odr-use; вам нужно будет только определить его, если взять его адрес или сформировать постоянную ссылку на него.

На случай, если это кому-нибудь поможет, у меня с GCC 4.7 с помощью constexpr сработало следующее:

template<class T> class X {
  constexpr static int s = 0;
};
template<class T> constexpr int X<T>::s; // link error if this line is omitted

Я не претендую на то, является ли это "правильным". Я оставлю это более квалифицированным.

Другие вопросы по тегам