Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Consider this following piece of code:

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
   }

The expression ai;, as; and as_bad are discarded value expressions and according to the C++ draft standard 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 ]

So the case (3) should be ill-formed.

Nevertheless, the behavior of compilers seems chaotic:

  1. GCC:

    • ai; => loads the value of ai;
    • as; => no code generated, no warning;
    • as_bad; => loads as_bad.i.
  2. Clang does not produce a load for case (2) and generates the warning: expression result unused; assign into a variable to force a volatile load [-Wunused-volatile-lvalue]

    • ai; => loads the value of ai;
    • as; => no code generated; warning expression result unused; assign into a variable to force a volatile load [-Wunused-volatile-lvalue]
    • as_bad; => same as as;.
  3. MSVC performs the load in both cases.

    • ai; => loads the value of ai;
    • as; => loads as.i (without calling to the volatile copy constructor)
    • as_bad; => loads as_bad.i.

Summary of what I expected according to the standard:

  • ai; => loads the value of ai;
  • as; => call S(const volatile S&) with as as argument;
  • as_bad; => generate a compilation error

Is my interpretation of the standard right? Which compiler is right if any?

like image 871
Oliv Avatar asked Mar 04 '18 11:03

Oliv


1 Answers

  1. C++03 said that the lvalue-to-rvalue conversion doesn't happen for the result of an expression statement, and doesn't explicitly say that a copy occurs when the conversion happens anyway.
  2. C++11 says, as you stated, that the conversion does happen for volatile objects, and that the conversion involves copying to make a temporary.
  3. C++14 merely cleans up the wording (to avoid silly things like b ? (x,y) : z not counting if y does) and adds the note about the volatile copy constructor.
  4. C++17 applies the temporary materialization conversion to preserve the previous meaning.

So my conclusion is that (as of C++11) you are correct and all the compilers are wrong. In particular, the S::i load shouldn't happen unless your copy constructor reads it. The implementation-defined nature of "access" is irrelevant to the question of what is well-formed, of course; it only affects whether the load instruction for ai is actually generated. There is the issue of S_bad being an aggregate, but that is irrelevant since it's not being list-initialized.

like image 151
Davis Herring Avatar answered Oct 22 '22 14:10

Davis Herring