Почему cppreference определяет тип_копирования xxx_v ярлыков как inline constexpr, а не только constexpr?

Почему cppreference определяет ярлыки xxx_v типа_traits как inline constexpr, а не только constexpr?

Например, см. is_integral_v:

template< class T >
inline constexpr bool is_integral_v = is_integral<T>::value;

Это только вопрос стиля или есть какая-то разница в поведении? Насколько я знаю, переменные constexpr неявно inline.

Изменить: смотря на черновик нового стандарта, он также использует inline constexpr. Тогда вопрос действительно относится к стандарту.

Ответы

Ответ 1

[basic.link]/3.2 применяется к именам "не встроенной переменной нестабильного типа с константой". Шаблон переменной не является переменной, так же как шаблон класса не является классом, шаблон функции не является функцией, а куттер-резак не является файлом cookie. Таким образом, это правило не применяется к самому шаблону is_integral_v.

Специализация переменной шаблона - это переменная, однако есть некоторые вопросы о том, дает ли это правило внутреннюю связь. Это основная проблема 1713, направление которой в том, что правило не применимо.

Основная проблема 1713, однако, не была своевременно разрешена для С++ 17. Поэтому LWG решила просто штукатурку inline по всем шаблонам переменных просто быть в безопасности, потому что они тоже не болят.

Ответ 2

[dcl.constexpr]/9

Спецификатор constexpr, используемый в объявлении объекта, объявляет объект как const.

[basic.link]/3.2

Имя, имеющее область пространства имен, имеет внутреннюю связь, если это имя

-a non-inline переменная из энергонезависимого const-квалифицированного типа, которая не является явно объявленной extern или ранее объявленной как внешняя связь

Без спецификатора inline, is_integral_v будет иметь внутреннюю связь. Это может быть проблематично, если вы сравнили два указателя на это же имя переменной, взятое в разных единицах перевода.


Nota Bene: спецификатор inline избыточен с constexpr, только если переменная является элементом статических данных класса.


Следуя примеру легкого нарушения одного правила определения, которое может произойти, если is_integral_v где не встроено.

bad_type_trait.h

template<class T>
constexpr auto bad_is_integral_v=std::is_integral<T>::value;

my_header.h

#include "bad_type_trait.h"
void f(const bool& x);

inline void g()
  {
  f(bad_is_integral_v<int>);
  //g ODR use the static variable bad_is_integral_v.
  //"ODR use" approximate definition is: 
  //     the variable is refered by its memory address.
  }

source1.cpp

#include "my_header.h"
void my_func1(){
   g(); //the definition of g in this translation unit.
   }

source2.cpp

#include "my_header.h"
void my_func2(){
   g(); //is not the same as the definition of g in this translation unit.
   }

В двух единицах перевода переменная bad_is_integral_v создается как отдельные статические переменные. Встроенная функция g() определена в этих двух единицах перевода. Внутри определения g() переменная bad_is_integral_v используется ODR, поэтому два определения g() различны, что является нарушением одного правила определения.