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

Oli*_*liv 11 c++ volatile language-lawyer

考虑以下这段代码:

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
   }
Run Code Online (Sandbox Code Playgroud)

表达式ai;,as;并且as_bad是废弃的值表达式,并根据C++草案标准N4659/[expr] .12我预计在这三种情况下将应用左值到右值.对于情况(2),这应该导致对volatile复制构造函数(S(const volatile S&))[expr]/12的调用

[...]如果表达式是此可选转换后的prvalue,则应用临时实现转换([conv.rval]).[注意:如果表达式是类类型的左值,则它必须具有易失性复制构造函数来初始化临时值,该临时值是左值到右值转换的结果对象. - 结束说明]

因此案例(3)应该是不正确的.

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

  1. GCC:

    • ai;=>加载值ai;
    • as; =>没有代码生成,没有警告;
    • as_bad;=>加载as_bad.i.
  2. Clang不会为case(2)生成负载并生成警告:表达结果未使用; 分配给变量以强制挥发性负载[-Wunused-volatile-lvalue]

    • ai;=>加载值ai;
    • as;=>没有生成代码; 警告表达结果未使用; 分配给变量以强制挥发性负载[-Wunused-volatile-lvalue]
    • as_bad;=>相同as;.
  3. MSVC在两种情况下都执行加载.

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

根据标准我的预期总结:

  • ai;=>加载值ai;
  • as;=> S(const volatile S&)as参数调用;
  • as_bad; =>生成编译错误

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

Dav*_*ing 3

  1. C++03 表示表达式语句的结果不会发生左值到右值的转换,并且没有明确表示无论如何发生转换时都会发生复制。
  2. 正如您所说,C++11 表示,转换确实发生在易失性对象上,并且转换涉及复制以生成临时对象。
  3. C++14 只是清理了措辞(以避免愚蠢的事情,例如b ? (x,y) : z不计算 if 的y情况)并添加有关易失性复制构造函数的注释。
  4. C++17 应用临时物化转换来保留先前的含义。

所以我的结论是(从 C++11 开始)你是正确的,所有编译器都是错误的。特别是,S::i除非您的复制构造函数读取它,否则不应发生加载。当然,“访问”的实现定义性质与格式良好的问题无关;它只影响是否ai真正生成加载指令。存在聚合的问题S_bad,但这无关紧要,因为它没有被列表初始化。