Подтвердить что ты не робот

Понимание статических переменных-членов constexpr

У меня есть некоторые путаницы в отношении переменных-членов static constexpr в С++ 11.

В first.hpp

template<typename T>
struct cond_I
{ static constexpr T value = 0; }; 


// specialization 
template<typename T>
struct cond_I< std::complex<T> >
{ static constexpr std::complex<T> value = {0,1}; }; 

В функции main()

cout << cond_I<double>::value << endl;            // this works fine
cout << cond_I< complex<double> >::value << endl; // linker error

Однако, если я добавлю следующую строку в first.hpp, все будет хорошо.

template<typename T1> 
constexpr std::complex<T1> cond_I< std::complex<T1> >::value;

То, что я понимаю (возможно, ошибаюсь), заключается в том, что cond_I< std::complex<double> >::value требуется определение, но в предыдущем случае оно имеет только объявление. Но тогда как насчет cond_I<double>::value? Почему это не требует определения?

Опять же, в другом файле заголовка second.hpp у меня есть:

В second.hpp

// empty struct
template<typename T>
struct eps
{ };


// special cases
template<>
struct eps<double>
{
  static constexpr double value = 1.0e-12;
};

template<>
struct eps<float>
{
  static constexpr float value = 1.0e-6;
};

В этом случае следующие коды отлично работают без определения eps<>::value.

В функции main()

cout << eps<double>::value << endl;    //  works fine
cout << eps<float>::value << endl;     //  works fine

Может кто-нибудь объяснит мне разные типы поведения переменных static constexpr в этих сценариях?

Эти поведения одинаковы для gcc-5.2 и clang-3.6.

4b9b3361

Ответ 1

В соответствии со стандартом 9.4.2/p3 Элементы статических данных [class.static.data] (Emphasis Mine):

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

Как ранее объяснил M.M в комментариях ostream::operator<<(ostream&, const complex<T>&), проходит по ссылке, поэтому значение считается odr-используемым в программе. Таким образом, поскольку приведенная выше формулировка диктует, вы должны дать определение.

Теперь, когда вы уже узнали, что основные типы передаются по значению, именно поэтому определение не требуется.