Выраженные выражения значений типа volatile ведут себя не так, как у летучих встроенных типов
Рассмотрим следующий фрагмент кода:
struct S{
int i;
S(int);
S(const volatile S&);
};
struct S_bad{
int i;
};
volatile S as{0};
volatile S_bad as_bad{0};
volatile int ai{0};
void test(){
ai; //(1)=> a load is always performed
as; //(2)=> Should call the volatile copy constructor
as_bad; //(3)=> Should be ill-formed
}
Выражение ai;
, as;
и as_bad
- это отклоненные выражения значений и в соответствии со стандартным проектом С++ N4659/[expr].12 Я ожидал, что lvalue-to-rvalue применил бы в этих трех случаях. Для случая (2) это должно вызвать вызов конструктора летучих копий (S(const volatile S&)
) [expr]/12
[...] Если выражение является prvalue после этого необязательного преобразования, применяется временное преобразование материализации ([conv.rval]). [Примечание. Если выражение является значением lvalue типа класса, у него должен быть конструктор летучих копий для инициализации временного объекта, который является результирующим объектом преобразования lvalue-to-rvalue. - конечная нота]
Таким образом, случай (3) должен быть плохо сформирован.
Тем не менее, поведение компиляторов кажется хаотичным:
-
GCC:
-
ai;
= > загружает значение ai
;
-
as;
= > не генерируется код, нет предупреждений;
-
as_bad;
= > загружает as_bad.i
.
-
Clang не создает нагрузку для случая (2) и генерирует предупреждение: результат выражения не используется; назначить в переменную для принудительной загрузки [-Wunused-volatile-lvalue]
-
ai;
= > загружает значение ai
;
-
as;
= > не генерируется код; результат выражения предупреждения не используется; назначить в переменную для принудительной загрузки [-Wunused-volatile-lvalue]
-
as_bad;
= > тот же, что и as;
.
-
MSVC выполняет загрузку в обоих случаях.
-
ai;
= > загружает значение ai
;
-
as;
= > загружает as.i
(без вызова конструктора летучих копий)
-
as_bad;
= > загружает as_bad.i
.
Резюме того, что я ожидал в соответствии со стандартом:
-
ai;
= > загружает значение ai
;
-
as;
= > вызов S(const volatile S&)
с as
в качестве аргумента;
-
as_bad;
= > генерировать ошибку компиляции
Является ли моя интерпретация стандарта правильной? Какой компилятор прав, если таковой имеется?
Ответы
Ответ 1
- С++ 03 сказал, что преобразование lvalue-to-rvalue не выполняется для результата оператора выражения и явно не говорит о том, что копия возникает, когда преобразование происходит в любом случае.
- С++ 11 говорит, как вы сказали, что преобразование происходит для изменчивых объектов и что преобразование включает в себя копирование, чтобы сделать временным.
- С++ 14 просто очищает формулировку (чтобы избежать таких глупых вещей, как
b ? (x,y) : z
, не считая, если y
), и добавляет примечание о конструкторе летучих копий.
- С++ 17 применяет временное преобразование материализации для сохранения предыдущего значения.
Итак, я пришел к выводу, что (как и на С++ 11) вы правы, и все компиляторы ошибаются. В частности, загрузка S::i
не должна выполняться, если ваш конструктор копирования не прочитает ее. Естественно, что "доступ", определенный реализацией, не имеет отношения к вопросу о том, что хорошо сформировано, конечно; это влияет только на то, действительно ли генерируется команда load для ai
. Существует проблема, заключающаяся в том, что S_bad
является агрегатом, но это не имеет значения, поскольку оно не инициализируется списком.