Выраженные выражения значений типа 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 является агрегатом, но это не имеет значения, поскольку оно не инициализируется списком.