WG21: N0321 X3J16: 93-0114 Date: 29 July 93 Author: John Max Skaller Reply: maxtal@suphys.physics.su.oz.au Clarification of Volatile Semantics ----------------------------------- Review ------ The Working Paper contains the following paragraphs. WP. 7.1.6 2 There are no implementation-independent semantics for volatile objects; volatile is a hint to the compiler to avoid aggressive optimization involving the object because the value of the object may be changed by means undetectable by a compiler. Each element of a volatile array is volatile and each nonfunction, nonstatic member of a volatile class object is volatile (f9.3.1). An object may be both const and volatile, with the type-specifiers appearing in either order. WP 8.4.3 4 If the initializer for a reference to type T is an lvalue of type T or of a type derived (f10) from T for which T is an unambiguous accessible base (f4.6), the reference will refer to the (T part of the) initializer; otherwise, if and only if the reference is to a const and an object of type T can be created from the initializer, such an object will be created. The reference then becomes a name for that object. For example, double d = 2.0; double& rd = d; // rd refers to `d' const double& rcd = d; // rcd refers to `d' double& rd2 = 2.0; // error: not an lvalue int i = 2; double& rd3 = i; // error: type mismatch const double& rcd2 = 2; // rcd2 refers to temporary // with value `2' Problem ------- It is not entirely clear whether it is permitted to bind a const volatile reference to a temporary. No example is given. Recommended resolution: ----------------------- "A const volatile reference must bind to an lvalue". The editor will include an example that clarifies this, as well as changes to the wording of 8.4.3/4. Argument. --------- A volatile object is often used to represent a hardware port or concurrency lock of some kind which may change its value independenly of the current thread of control. As such, the compiler ought not optimise away access to it. Volatile member functions enable whole volatile objects to be accessed consistently, perhaps by the user providing locks covering access to multiple members, or other special techniques. Volatile can also be used to prevent aggressive optimisation of potentially aliased reference parameters. While it makes sense to allow a const reference to bind to a temporary, since being const no changes can be made to the value, and being temporary no aliasing is possible, it does not makes sense to allow a volatile reference to bind to a non-lvalue (nor is it currently permitted), and I believe this applies even when the reference is also const. A const volatile reference to a hardware port or lock must refer to that location: if one only wanted to examine the value at one time, one would not use a reference but copy the value. The constness here inhibits changes to the object, the volatility inhibits copying. For example: void wait(int const volatile& flag) { while(flag); } void process(int const volatile & flag) { wait(flag); switch(flag) { ... } } void transaction(int volatile & flag) { process(flag); flag=0; } Here it is critcal that the actual hardware port 'flag' is accessed directly, and not a copy. Had a copy been created, the 'wait' routine would enter an infinite loop. These routines could be implemented without const and would provide the appropriate semantics, but the loss of safety involved especially in such cases is a strong argument to adopt the above resolution. There is a second reason to adopt this resolution. A simplified overloading scheme might be adopted that matches arguments to 1) T& 2) volatile T& 3) const volatile T& 4) const T&, T in this order. A match to any entry implies a match to all entries lower (or equal). This mechanism makes no sense if a const volatile reference may bind to copies, instead the scheme 1) T& 2) volatile T& 3) const T&, const volatile T&, T would have to be adopted. But in this case not only are the functions f(const T&), f(T) and f(const volatile T&) indistinguishable, but the 'volatile' is clearly an implementation detail and no part of the signature of the function (since it has no effect on overload resolution, determining the type of the parameter in the function body only). As such it would be consistent to adopt a resolution similar to the one taken at Munich that ignores top level const or volatile in pass by value, that the volatile in a const volatile reference is also ignored. But this is particularly nasty. For both these reasons I recommend that const volatile references may only bind to an lvalue. Changes to the WP. ------------------ The changes to the WP are minimal, I repeat 8.4.3/4 with the changes emphasised: 4 If the initializer for a reference to type T is an lvalue of type T or of a type derived (f10) from T for which T is an unambiguous accessible base (f4.6), the reference will refer to the (T part of the) initializer; otherwise, if and only if the reference is to a **non volatile** const and an object of type T can be created from the initializer, such an object will be created. The reference then becomes a name for that object. For example, double d = 2.0; double& rd = d; // rd refers to `d' const double& rcd = d; // rcd refers to `d' double& rd2 = 2.0; // error: not an lvalue int i = 2; double& rd3 = i; // error: type mismatch const double& rcd2 = 2; // rcd2 refers to temporary // with value `2' **** const volatile double & rcvd = 2; // error: lvalue required **** (Insert the words "non-volatile" and the extra example as indicated.)