Вероятно, это немного необычный вопрос, поскольку он требует более полного объяснения короткого ответа, данного Другой вопрос и некоторых аспектов стандарта C ++ 11, связанных с ним.
Для удобства я подведу итоги упомянутого здесь вопроса. ОП определяет класс:
struct Account
{
static constexpr int period = 30;
void foo(const int &) { }
void bar() { foo(period); } //no error?
};
и задается вопросом, почему он не получает ошибки об использовании инициализированного в классе статического члена данных (в книге упоминается, что это незаконно). В ответе Йоханнеса Шауба говорится, что:
Несмотря на то, что я полагаюсь на источник и достоверность этого ответа, мне, честно говоря, не нравится его, потому что я лично нахожу его слишком загадочным, поэтому я попытался выработать более значимый ответ сам, с частичным успехом. Соответствующим представляется п. 9.4.2 / 4:
«Должно быть одно определение статического члена данных, УСО используемый (3.2) в программе; Диагностика не требуется« [Акценты мои]
Что приближает меня к сути. И вот как § 3.2 / 2 определяет УСО используемый переменная:
«Переменная, имя которой появляется в качестве потенциально вычисляемого выражения, используется в odr если это объект, который удовлетворяет требованиям для появления в константном выражении (5.19) а также преобразование lvalue в rvalue (4.1) немедленно применяется « [Акценты мои]
В вопросе ОП переменная period
четко удовлетворяет требованиям для появления в постоянном выражении, будучи constexpr
переменная. Так что причина, безусловно, должна быть найдена в второй состояние: «и преобразование lvalue в rvalue (4.1) немедленно применяется«.
Вот где у меня проблемы с интерпретацией стандарта. Что на самом деле означает это второе условие? Какие ситуации это охватывает? Означает ли это, что статический constexpr
переменная не odr-used (и, следовательно, может быть инициализирован в классе), если он возвращается из функции?
В более общем смысле: Что вам разрешено делать со статическим constexpr
переменная, чтобы вы могли в классе инициализировать его?
Означает ли это, что статическая переменная constexpr не используется odr (и
поэтому может быть инициализирован в классе), если он возвращается из
функционировать?
Да.
По сути, до тех пор, пока вы рассматриваете это как значение, а не объект, тогда он не используется. Учтите, что если вы вставили значение, код работал бы одинаково — это когда оно обрабатывается как значение. Но есть некоторые сценарии, где это не так.
Есть только несколько сценариев, в которых преобразование lvalue в rvalue не выполняется на примитивах, и это ссылка привязки, &obj
и, возможно, пару других, но это очень мало. Помните, что если компилятор дает вам const int&
ссылаясь на period
, тогда вы должны иметь возможность взять его адрес, и, кроме того, этот адрес должен быть тем же за каждый ТУ. Это означает, что в ужасающей системе TU C ++ должно быть одно явное определение.
Если он не используется odr, компилятор может сделать копию в каждом TU, или заменить значение, или что угодно, и вы не сможете увидеть разницу.
Вы пропустили часть предпосылки. Определение класса, приведенное выше, полностью допустимо, если вы также определите Account::period
где-то (но без предоставления инициализатора). См. Последнюю отправку 9.4.2 / 3:
Член по-прежнему должен быть определен в области пространства имен, если он используется odr
(3.2) в программе и определение объема пространства имен не должно
содержать инициализатор.
Все это обсуждение применимо только к статическим элементам данных constexpr, но не к статическим переменным области имен. Когда члены статических данных constexpr
(а не просто const) вы должен инициализировать их в определении класса. Таким образом, ваш вопрос должен быть: Что вам разрешено делать со статическим членом данных constexpr, чтобы вам не приходилось предоставлять его определение в области пространства имен?
Исходя из предыдущего, ответ заключается в том, что член не должен быть УСО используемый. И вы уже нашли соответствующие части определения УСО используемый. Таким образом, вы можете использовать член в контексте, где он не потенциально оценены — как неоцененный операнд (например, sizeof
или же decltype
) или его подвыражение. И вы можете использовать его в выражении, где преобразование lvalue в rvalue применяется немедленно.
Так что теперь мы до Какое использование переменной вызывает немедленное преобразование lvalue в rvalue?
Часть этого ответа в §5 / 8:
Всякий раз, когда выражение glvalue появляется как операнд оператора
который ожидает prvalue для этого операнда, lvalue-to-rvalue (4.1),
стандарт массив-указатель (4.2) или функция-указатель (4.3)
преобразования применяются для преобразования выражения в значение.
Для арифметических типов это по существу относится ко всем операторам, которые применяют стандартные арифметические преобразования. Таким образом, вы можете использовать член в различных арифметических и логических операциях без необходимости определения.
Я не могу перечислить все вещи, которые вы можете или не можете сделать здесь, потому что требования о том, что что-то должно быть [g] lvalue или [p] rvalue, распространены по всему стандарту. Эмпирическое правило: если бы только значение переменной используется преобразование lvalue в rvalue; если переменная используется в качестве объект, используется как lvalue.
Вы не можете использовать его в тех случаях, когда явно требуется lvalue, например, в качестве аргумента для оператора address-of или операторов-мутантов). Прямое связывание ссылок lvalue (без преобразования) является таким контекстом.
Еще несколько примеров (без подробного стандартного анализа):
Вы можете передать его функциям, если только параметр функции не является ссылкой, с которой переменная может быть напрямую связана.
Для возврата из функции: если неявное преобразование в возвращаемый тип включает в себя определяемую пользователем функцию преобразования, применяются правила передачи в функцию. В противном случае вы можете вернуть его, если только функция не возвращает lvalue (ссылку), ссылающуюся непосредственно на переменную.
Основное правило для УСО используемый переменных, «Одно правило определения» находится в 3.2 / 3:
Каждая программа должна содержать ровно одно определение каждого не встроенного
функция или переменная, которая используется в этой программе; нет диагностики
требуется.
Часть «не требует диагностики» означает, что программы, нарушающие это правило, вызывают неопределенное поведение, которое может варьироваться от неуспешной компиляции, неожиданной компиляции и сбоя до компиляции и действия, как будто все в порядке. И ваш компилятор не должен предупреждать вас о проблеме.
Причина, как уже указывали другие, заключается в том, что многие из этих нарушений будут обнаружены только компоновщиком. Но оптимизация могла удалить ссылки на объекты, так что не остается никакой причины для сбоя связи, иначе связывание может иногда происходить только во время выполнения или может быть определено для выбора произвольного экземпляра из нескольких определений имени.