volatile class 类型的丢弃值表达式与 volatile 内置类型的行为不同

Discarded value expressions of volatile class type behave differently than those of volatile built-in types

考虑以下这段代码:

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 是废弃的值表达式,根据 C++ 草案标准 N4659/[expr].12 I expected that an lvalue-to-rvalue would have applied in these three cases. For case (2) this should cause a call to the volatile copy constructor (S(const volatile S&)) [expr]/12

[...]If the expression is a prvalue after this optional conversion, the temporary materialization conversion ([conv.rval]) is applied. [ Note: If the expression is an lvalue of class type, it must have a volatile copy constructor to initialize the temporary that is the result object of the lvalue-to-rvalue conversion. — end note ]

所以情况 (3) 应该是病式的。

然而,编译器的行为似乎很混乱:

  1. 海湾合作委员会:

    • ai; => 加载 ai 的值;
    • as; => 没有生成代码,没有警告;
    • as_bad; => 加载 as_bad.i.
  2. Clang 不会为情况 (2) 生成负载并生成警告:expression result unused;分配到一个变量中以强制进行易失性加载 [-Wunused-volatile-lvalue]

    • ai; => 加载 ai 的值;
    • as; => 没有生成代码;警告 表达式结果未使用;赋值到变量中以强制进行易失性加载 [-Wunused-volatile-lvalue]
    • as_bad; => 与 as;.
    • 相同
  3. MSVC 在这两种情况下执行加载。

    • ai; => 加载 ai 的值;
    • as; => 加载 as.i(不调用可变复制构造函数)
    • as_bad; => 加载 as_bad.i.

根据标准总结一下我的预期:

我对标准的解释正确吗?哪个编译器是正确的?

  1. C++03 表示表达式语句的结果不会发生 lvalue-to-rvalue 转换,也没有明确说明转换发生时会发生复制。
  2. C++11 表示,如您所说,转换确实发生在易失性对象上,并且转换涉及复制以创建临时对象。
  3. C++14 只是清理了措辞(以避免像 b ? (x,y) : z 这样愚蠢的事情,如果 y 算的话)并添加了关于 volatile 复制构造函数的注释。
  4. C++17 应用临时物化转换来保留以前的含义。

所以我的结论是(从 C++11 开始)你是对的,所有的编译器都是错的。特别是, S::i 加载不应该发生,除非你的复制构造函数读取它。当然,"access" 的 implementation-defined 性质与什么是 well-formed 的问题无关;它只影响 ai 的加载指令是否实际生成。存在 S_bad 作为聚合的问题,但这无关紧要,因为它不是 list-initialized.