C ++ 17 вводит встроенные переменные
C ++ 17 исправляет эту проблему для constexpr static
переменных-членов, требующих внепланового определения, если оно было использовано odr. См. Вторую половину этого ответа для деталей до C ++ 17.
Предложение P0386 Inline Variables предоставляет возможность применять inline
спецификатор к переменным. В частности, к этому случаю constexpr
относятся inline
статические переменные-члены. Предложение говорит:
Встроенный спецификатор может применяться как к переменным, так и к функциям. Переменная, объявленная inline, имеет ту же семантику, что и функция, объявленная inline: она может быть определена идентично в нескольких единицах перевода, должна быть определена в каждой единице перевода, в которой она используется в odr, и поведение программы такое, как если бы есть ровно одна переменная.
и модифицировал [basic.def] p2:
Объявление является определением, если
...
- он объявляет член статических данных вне определения класса, и переменная была определена в классе с помощью спецификатора constexpr (это использование устарело; см. [depr.static_constexpr]),
...
и добавьте [depr.static_constexpr] :
Для совместимости с предыдущими международными стандартами C ++ член статических данных constexpr может быть избыточно объявлен вне класса без инициализатора. Это использование не рекомендуется. [ Пример:
struct A {
static constexpr int n = 5; // definition (declaration in C++ 2014)
};
constexpr int A::n; // redundant declaration (definition in C++ 2014)
- конец примера]
C ++ 14 и более ранние
В C ++ 03 нам было разрешено предоставлять только инициализаторы в классе для константных интегралов или константных типов перечисления , в C ++ 11 использование constexpr
этого было распространено на литеральные типы .
В C ++ 11 нам не нужно предоставлять определение области имен для статического constexpr
члена, если он не используется odr , это можно увидеть из черновика стандартного раздела C ++ 11 9.4.2
[class.static.data], в котором говорится ( акцент мой идет вперед ):
[...] Статический член данных литерального типа может быть объявлен в определении класса с помощью спецификатора constexpr; если это так, в его объявлении должна быть указана инициализация-скобка или равный-инициализатор, в которой каждое предложение-инициализатор, являющееся выражением присваивания, является константным выражением. [Примечание: в обоих этих случаях член может появляться в константных выражениях. - примечание конца]
Элемент все еще должен быть определен в области пространства имен, если он используется odr (3.2) в программе, и определение области пространства имен не должно содержать инициализатор.
Итак, возникает вопрос, baz
используется ли здесь odr :
std::string str(baz);
и ответ - да , и поэтому нам также требуется определение области имен.
Так как же определить, используется ли переменная odr ? Оригинальная формулировка C ++ 11 в разделе 3.2
[basic.def.odr] гласит:
Выражение потенциально оценивается, если оно не является неоцененным операндом (раздел 5) или его подвыражением. Переменная, имя которой появляется в качестве потенциально оцениваемого выражения, используется odr, если только
она не является объектом, удовлетворяющим требованиям для появления в константном выражении (5.19), и преобразование lvalue-в-значение (4.1) применяется немедленно .
Таким baz
образом, получается константное выражение, но преобразование lvalue-to-rvalue применяется не сразу, так как оно неприменимо из-за того, baz
что является массивом. Это 4.1
описано в разделе [conv.lval], где говорится:
Glvalue (3.10) нефункционального типа, не являющегося массивом T, может быть преобразован в prvalue.53 [...]
Что применяется при преобразовании массива в указатель .
Эта формулировка [basic.def.odr] была изменена из-за Отчета о дефектах 712, поскольку некоторые случаи не были охвачены этой формулировкой, но эти изменения не изменяют результаты для этого случая.