Что мне разрешено делать со статическим, constexpr, членом данных, инициализированным в классе?

Вероятно, это немного необычный вопрос, поскольку он требует более полного объяснения короткого ответа на еще один вопрос и некоторые связанные с ним аспекты стандарта C++11.

Для простоты ссылки я подытожу упомянутый вопрос здесь. ОП определяет класс:

struct Account 
{
    static constexpr int period = 30;
    void foo(const int &) { }
    void bar() { foo(period); } //no error?
};

и задается вопросом, почему он не получает ошибки об использовании инициализированного статического члена данных в классе (в книге упоминается, что это незаконно). В ответе Йоханнеса Шауба говорится, что:

  1. Это нарушает правило одного определения;
  2. Диагностика не требуется.

Как бы я ни полагался на источник и достоверность этого ответа, он мне, честно говоря, не нравится, потому что лично я нахожу его слишком загадочным, поэтому я попытался самостоятельно разработать более содержательный ответ, но с частичным успехом. Уместным кажется § 9.4.2/4:

"Должно быть ровно одно определение статического члена данных, который используется ODR (3.2) в программе; диагностика не требуется" [выделено мной]

Что немного приближает меня к сути. А вот как в § 3.2/2 определяется переменная odr-used:

"Переменная, имя которой появляется как потенциально оцениваемое выражение, используется odr, если только это объект, который удовлетворяет требованиям для появления в постоянном выражении (5.19) и немедленно применяется преобразование lvalue-to-rvalue (4.1)" [выделено мной]

В вопросе OP переменная period явно удовлетворяет требованиям для появления в постоянном выражении, будучи переменной constexpr. Так что причина наверняка должна быть найдена во втором условии: "и тут же применяется преобразование lvalue-to-rvalue (4.1)".

Вот где у меня проблемы с интерпретацией стандарта. Что на самом деле означает это второе условие? Какие ситуации оно охватывает? Означает ли это, что статическая переменная constexpr не используется odr (и, следовательно, может быть инициализирована в классе), если она возвращается из функции?

В более общем плане: Что вам разрешено делать со статической переменной constexpr, чтобы вы могли инициализировать ее в классе?


person Andy Prowl    schedule 27.01.2013    source источник
comment
Я вижу, что у нас очень соответствующие, но отдельные вопросы. Оба возникают из одной и той же базы Q и отвечают :)   -  person Alok Save    schedule 27.01.2013
comment
@AlokSave: действительно так кажется. По крайней мере, я не единственный, кто находит все это немного запутанным.   -  person Andy Prowl    schedule 27.01.2013
comment
Честно говоря, я немного почесал затылок, пытаясь понять смысл стандартного решения, но я не мог ясно его разглядеть. На самом деле вам удалось довольно хорошо выразить мыслительный процесс в вашем вопросе. Я сделал более общий вопрос, опасаясь, что мое восприятие стандартного языка может на самом деле повлиять на кого-то, кто читает его, чтобы он имел смысл. +1 за документирование вашего исследования :)   -  person Alok Save    schedule 27.01.2013
comment
@AlokSave: мне понравилось опасение, что мое восприятие стандартного языка может фактически повлиять на то, что кто-то читает его, чтобы понять его часть. Я сталкиваюсь с этим чувством почти каждый раз, когда мне приходится спрашивать значение какого-то формального определения, которое мне непонятно (не только в C++ или программировании).   -  person Andy Prowl    schedule 27.01.2013
comment
я не использовал загадочный термин одно правило определения в своем ответе. только когда кто-то по какой-то причине хотел узнать, где в стандарте находится упомянутое мною правило, я упомянул его.   -  person Johannes Schaub - litb    schedule 27.01.2013
comment
@JohannesSchaub-litb: незашифрованное правило определения одного термина не включено в ваш ответ, верно, но этот факт просто делает ваш ответ загадочным. Ваш приговор Нарушение этого правила не требует диагностики. Таким образом, поведение фактически не определено. указывает что, и я этому верю, но не объясняет почему, и мне это не нравится.   -  person Andy Prowl    schedule 27.01.2013
comment
@andy, ссылаясь на одно правило определения, не указывает, почему. в нем говорится, что да, это имя упомянутого правила. это кажется самым бесполезным, чтобы сказать вопрошающему. есть большая разница объяснить кому-то Стандарт и объяснить кому-то С++.   -  person Johannes Schaub - litb    schedule 27.01.2013
comment
легкое для понимания объяснение поведения было дано ему в книге. ему не нужно знать о концепциях Стандарта, которые используются для придания такого поведения. Он их и не просил. Его вопрос был исключительно о том, почему его компилятор не дает диагностики, и мой ответ был кратким и точным.   -  person Johannes Schaub - litb    schedule 27.01.2013
comment
@JohannesSchaub-litb: Я не говорю, что ODR указывает, почему, я говорю, что вы должны были объяснить OP почему. Правда, никакой диагностики не требуется, но где это указано? Я знаю, что в большинстве случаев цитирование стандарта не требуется, но этот вопрос нетривиален, и тест казался опровергает утверждение в книге. Так что в этом случае более подробное объяснение было бы уместно. Но это только мое мнение. Я отредактирую свой вопрос, чтобы было ясно, что это всего лишь мое мнение.   -  person Andy Prowl    schedule 27.01.2013
comment
где это указано, это не ответ, почему не требуется диагностика. из того факта, что спрашивающий не упомянул о каком-либо интересе к Стандарту и, очевидно, производил впечатление неспособного понять его в настоящее время, я сделал разумный выбор не путать его с понятиями Стандарта.   -  person Johannes Schaub - litb    schedule 27.01.2013
comment
@JohannesSchaub-litb: Где это указано, дает объективное подтверждение того, что книга верна, и именно это ОП (и ответы, данные другими надежными пользователями) неявно, казалось, задавали вопросы ИМО. Но неважно, опять же, это всего лишь мое мнение о качестве вашего ответа, я всегда доверял его правильности (и проголосовал за него после редактирования).   -  person Andy Prowl    schedule 27.01.2013
comment
@andy, как бы я ни был впечатлен тем, что вы считаете меня объективным подтверждением, я должен сказать, что ссылки на Стандарт в ответах на SO ничего не стоят. другой парень (по иронии судьбы первый, кто спросил о местонахождении правила в Стандарте в моем ответе) опубликовал уморительно неправильно процитированную часть Стандарта в качестве ответа на тот же вопрос.   -  person Johannes Schaub - litb    schedule 27.01.2013
comment
@JohannesSchaub-litb: должно быть, произошло недоразумение. Я испытываю к вам огромное уважение, но я не хотел сказать, что вы являетесь объективным подтверждением. Я хотел сказать, что упоминание той части Стандарта, где сформулировано это правило (и, возможно, объяснение, если правило неясно или трудно интерпретировать), дает объективное подтверждение. Конечно, как вы говорите, можно процитировать и неправильную часть. Но это только облегчает опровержение, как это было в случае с неправильным ответом. Сведение вещей к формальным терминам всегда помогает устранить неоднозначность и понять.   -  person Andy Prowl    schedule 27.01.2013
comment
два стандартных нуба, сражающихся друг с другом со случайными стандартными цитатами, не делают результат более объективно подтвержденным, чем если бы был только один из них.   -  person Johannes Schaub - litb    schedule 27.01.2013
comment
@JohannesSchaub-litb: Это спорно: все споры создают путаницу, но формальные конфликты решить легче. И в любом случае, поскольку вы далеко не нуб Стандарта, вы могли бы предоставить эту ссылку.   -  person Andy Prowl    schedule 27.01.2013


Ответы (2)


Означает ли это, что статическая переменная constexpr не используется в odr (и, следовательно, может быть инициализирована в классе), если она возвращается из функции?

да.

По сути, если вы рассматриваете его как значение, а не как объект, он не используется odr. Учтите, что если вы вставите значение, код будет работать идентично — это когда он обрабатывается как rvalue. Но есть сценарии, в которых этого не будет.

Есть только несколько сценариев, в которых преобразование lvalue-to-rvalue не выполняется для примитивов, и это привязка ссылок, &obj и, возможно, пара других, но их очень мало. Помните, что если компилятор дает вам const int&, относящийся к period, то вы должны иметь возможность взять его адрес, и, кроме того, этот адрес должен быть одним и тем же для каждой TU. Это означает, что в ужасной системе TU C++ должно быть одно явное определение.

Если он не используется odr, компилятор может сделать копию в каждой TU, или подставить значение, или что угодно, и вы не заметите разницы.

person Puppy    schedule 27.01.2013
comment
Я понимаю дух и обоснование, +1, но у меня все еще есть некоторые сомнения относительно технических деталей. Означает ли это, что это будет законно: foo(static_cast<int&&>(period))? - person Andy Prowl; 27.01.2013
comment
Я на самом деле не знаю. Если в этом контексте встречается lvalue-to-rvalue, и это довольно неясно, поэтому я не уверен, так ли это (не думаю, что вам нужно проверить стандарт), тогда это законно, если нет, то это нет, так как ссылка rvalue не будет привязана к константному lvalue. - person Puppy; 27.01.2013
comment
Кажется, что вывод static_cast в этом случае является prvalue, что означает, что это законно. Согласно 5.2.9/1: Результатом выражения static_cast‹T›(v) является результат преобразования выражения v в тип T. Если T является ссылочным типом lvalue или ссылкой rvalue на тип функции, результат — lvalue; если T является ссылкой rvalue на тип объекта, результатом является значение x; в противном случае результатом является prvalue. - person Andy Prowl; 27.01.2013
comment
Здесь если T является ссылкой rvalue на тип объекта, результатом является значение x; применяется (int является типом объекта). Xvalue — это glvalue, а не prvalue, поэтому преобразование lvalue в rvalue не происходит. (Смысл приведения к ссылке rvalue заключается в том, что вы можете угрожать исходному объекту как rvalue, не копируя значение. - person JoergB; 27.01.2013
comment
Напишите сначала: Да: ответ «нет» для функции, которая возвращает int const& или возвращает тип класса, у которого есть конструктор преобразования, принимающий аргумент int const&. - person JoergB; 27.01.2013
comment
Я почти уверен, что подразумевалось, что он возвращается по значению. Но это может быть только я. - person Puppy; 27.01.2013
comment
@DeadMG: да, я имел в виду возврат по значению. - person Andy Prowl; 27.01.2013

Вы пропустили часть предпосылки. Приведенное выше определение класса полностью допустимо, если вы также где-то определяете Account::period (но без инициализатора). См. последнее предложение 9.4.2/3:

Член по-прежнему должен быть определен в области пространства имен, если он используется odr (3.2) в программе, и определение области пространства имен не должно содержать инициализатор.

Все это обсуждение относится только к статическим членам данных constexpr, а не к статическим переменным области пространства имен. Когда статические элементы данных являются constexpr (а не просто константами), вы должны инициализировать их в определении класса. Таким образом, ваш вопрос на самом деле должен звучать так: Что вам разрешено делать со статическим элементом данных constexpr, чтобы вам не приходилось предоставлять его определение в области пространства имен?

Из предыдущего следует, что элемент не должен использоваться odr-used. И вы уже нашли соответствующие части определения odr-used. Таким образом, вы можете использовать член в контексте, где он не потенциально оценивается — как невычисленный операнд (например, sizeofили decltype) или его подвыражение. И вы можете использовать его в выражении, где немедленно применяется преобразование lvalue-to-rvalue.

Итак, теперь мы подошли к Какое использование переменной вызывает немедленное преобразование lvalue в rvalue?

Часть этого ответа находится в §5/8:

Всякий раз, когда выражение glvalue появляется в качестве операнда оператора, который ожидает значение prvalue для этого операнда, выполняются стандартные преобразования lvalue-to-rvalue (4.1), массива в указатель (4.2) или функции в указатель (4.3). применяется для преобразования выражения в prvalue.

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

Я не могу перечислить все, что вы можете или не можете делать здесь, потому что требования, чтобы что-то было [g]lvalue или [p]rvalue, разбросаны по стандарту. Практическое правило: если используется только значение переменной, применяется преобразование lvalue в rvalue; если переменная используется как объект, она используется как lvalue.

Вы не можете использовать его в контекстах, где явно требуется lvalue, например, в качестве аргумента для оператора адреса или изменяющих операторов). Прямая привязка ссылок lvalue (без преобразования) является таким контекстом.

Еще несколько примеров (без подробного стандартизированного анализа):

Вы можете передать его функциям, если только параметр функции не является ссылкой, к которой переменная может быть напрямую привязана.

Для возврата из функции: если неявное преобразование в возвращаемый тип включает определяемую пользователем функцию преобразования, применяются правила передачи в функцию. В противном случае вы можете вернуть его, если только функция не возвращает lvalue (ссылку), ссылающуюся непосредственно на переменную.

Ключевое правило для переменных odr-used, «Правило одного определения» в 3.2/3:

Каждая программа должна содержать ровно одно определение каждой не встроенной функции или переменной, которая используется в этой программе odr; диагностика не требуется.

Часть «диагностика не требуется» означает, что программы, нарушающие это правило, вызывают неопределенное поведение, которое может варьироваться от неудачной компиляции, компиляции и неожиданного сбоя до компиляции и действия, как если бы все было в порядке. И вашему компилятору не нужно предупреждать вас о проблеме.

Причина, как уже указывали другие, заключается в том, что многие из этих нарушений могут быть обнаружены только компоновщиком. Но оптимизации могут удалить ссылки на объекты, так что не останется причин для сбоя связывания, иначе связывание может иногда происходить только во время выполнения или определяться для выбора произвольного экземпляра из нескольких определений имени.

person JoergB    schedule 27.01.2013