C ++ 17 вводит встроенные переменные
C ++ 17 исправляет эту проблему для статических переменных-членов constexpr, требующих внешнего определения, если оно использовалось.См. Исходный ответ ниже для деталей до C ++ 17.
Предложение P0386 Встроенные переменные предоставляет возможность применять встроенный спецификатор к переменным.В частности, в этом случае constexpr подразумевает inline для статических переменных-членов.Предложение гласит:
Встроенный спецификатор может применяться как к переменным, так и к функциям.Переменная, объявленная inline, имеет ту же семантику, что и функция, объявленная inline: она может быть определена идентично в нескольких единицах перевода, должна быть определена в каждой единице перевода, в которой она выделяется, и поведение программы такое, как если быровно одна переменная.
и измененный [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 ++ 03 нам было разрешено предоставлять только инициализаторы класса для константные интегралы или константные типы перечисления , в C ++ 11 с использованием constexpr это было расширено до литеральных типов .
ВВ C ++ 11 нам не нужно предоставлять определение области имен для статического члена constexpr, если он не используется odr , это можно увидеть из черновика стандартного раздела C ++ 11 9.4.2
[class.static.data] , в котором говорится ( выделение будет идти вперед ):
[...] Статический член данных литерального типа может бытьобъявлено в определении класса со спецификатором constexpr;если это так, в его объявлении должна быть указана инициализация-скобка или равный-инициализатор, в которой каждое предложение инициализатора, являющееся выражением присваивания, является константным выражением[Примечание: в обоих этих случаях член может появляться в константных выражениях.- конец примечания] Элемент по-прежнему должен быть определен в области имен, если он используется в программе (3.2) , и определение области имен не должно содержать инициализатор.
Итак, возникает вопрос: baz
odr-used здесь:
std::string str(baz);
и ответ yes , поэтому нам требуется пространство именопределение области действия.
Так как же нам определить, используется ли переменная 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 можно преобразовать в значение.53 [...]
Что применяется в преобразовании массива в указатель .
ЭтогореЗначение [basic.def.odr] было изменено из-за Отчет о дефектах 712 , поскольку некоторые случаи не были охвачены этой формулировкой, но эти изменения не изменяют результаты для этого случая. 1118 *