#include <iostream>
struct A{
A() = default;
A(volatile const A&){}
void show()const volatile {
}
};
int main(){
volatile A a;
//A b = std::move(a); // ill-formed
std::move(a).show(); //OK
}
Consider the example, the results of the example are out of my understanding about some relevant rules.
For A b = std::move(a);
, it's ill-formed, because it violates the following rule, that is:
dcl.init.ref#5.2
Otherwise, if the reference is an lvalue reference to a type that is not const-qualified or is volatile-qualified, the program is ill-formed.
That means, a lvalue reference to const volatile-qualified T cannot bind to any rvalue even though they're reference-compatible. A b = std::move(a);
obviously violates this rule, hence it's ill-formed.
However I don't know why compile std::move(a).show();
without reporting wrong. According to this rule:
For non-static member functions, the type of the implicit object parameter is
“lvalue reference to cv X” for functions declared without a ref-qualifier or with the & ref-qualifier
The type of the implicit object parameter of member function show
will be volatile const A&
. In general, it definitely violates [dcl.init.ref#5.2]. If change the definition of member function show
to:
void show() volatile const& {
}
std::move(a).show();
will be ill-formed. So must be some magic in the following rule that make std::move(a).show();
be compiled before changing show
. The rule is:
over.match.funcs#general-5
For non-static member functions declared without a ref-qualifier, an additional rule applies:
even if the implicit object parameter is not const-qualified, an rvalue can be bound to the parameter as long as in all other respects the argument can be converted to the type of the implicit object parameter.
Honestly, I really don't know what does the wording "in all other respects" mean? And what does the "the type of the implicit object parameter" refer to? Does the "type" refer to volatile const A&
or the referenced type volatile const A
? The wording is very vague. Anyhow, lvalue reference to const volatile T cannot bind to any rvalue of type T
. So, how to interpret that?
As a contrast:
#include <iostream>
struct B{
void show(){}
};
int main(){
volatile B b;
std::move(b).show(); //ill-formed
}
the type of the implicit object parameter of show
would be B&
, According to [over.match.funcs#general-5], even though ignore const-qualifier
, it's still ill-formed due to it discards the volatile-qualifier
. From this example, it implies that, For this sentence "in all other respects the argument can be converted to the type of the implicit object parameter", where the type should refer to reference type rather than the type the reference refers to. If the magic is this, it's still not sufficient to make std::move(a).show();
to be well-formed.
So, how to interpret these issues? I don't know how to use [over.match.funcs#general-5] to interpret these two examples.
The volatile qualifier declares a data object that can have its value changed in ways outside the control or detection of the compiler (such as a variable updated by the system clock or by another program).
Volatile functions are functions in which the value changes each time the cell is calculated. The value can change even if none of the function's arguments change. These functions recalculate every time Excel recalculates. For example, imagine a cell that calls the function NOW .
Yes a C++ variable be both const and volatile. It is used in situations like a read-only hardware register, or an output of another thread. Volatile means it may be changed by something external to the current thread and Const means that you do not write to it (in that program that is using the const declaration).
The const keyword specifies that the pointer cannot be modified after initialization; the pointer is protected from modification thereafter. The volatile keyword specifies that the value associated with the name that follows can be modified by actions other than those in the user application.
struct A { A() = default; A(volatile const A &) {} void show() const volatile {} }; int main() { volatile A a; std::move(a).show(); // OK }
The implied object parameter for the member function show()
is, as per [over.match.funcs]/4, const volatile A&
, such that for the purpose of overload resolution, we may, as per [over.match.funcs]/5, consider the data member function as
void show(const volatile A&);
Now, with this in mind, let's first simplify the example, with the purpose of:
A
seemingly may bind to an implied object parameter or type const volatile A&
but not to say a function parameter of the same type when the parameter is for a regular free function.Thus, consider the following simplified example:
#include <memory>
struct A {
void show() const volatile {}
};
void g(const volatile A &) { }
int main() {
volatile A a;
g(std::move(a)); // (i) Error.
std::move(a).show(); // (ii) OK.
}
The error message at (i), in GCC (10.1.0) is:
error: cannot bind non-const lvalue reference of type
const volatile A&
to an rvalue of typestd::remove_reference<volatile A&>::type
{akavolatile A
}
which is expected (as you have noted yourself) as per [dcl.init.ref]/5.2, which disallows rvalues to bind, in initialization, to volatile
references, even if they are const
-qualified.
Then why is (ii) accepted? Or, conversely, why does the restriction of [dcl.init.ref]/5.2 apparently not apply for the similar case of the implicit object parameter of a member function?
The answer lies in [over.match.funcs ]/5.1, which contains an exception for member functions declared without a ref-qualifier:
[over.match.funcs ]/5 [...] For non-static member functions declared without a ref-qualifier, an additional rule applies:
- /5.1 even if the implicit object parameter is not const-qualified, an rvalue can be bound to the parameter as long as in all other respects the argument can be converted to the type of the implicit object parameter.
[over.match.funcs ]/5.1 removes the prohibation of [dcl.init.ref]/5 regarding rvalues (rvalue binding), and the remaining criteria is applied to whether argument (rvalue ignored; volatile A
) can be ("in all other respects") converted to the implicit object parameter (const volatile A&
). As the implicit object parameter, as shown above, in this case is always an lvalue reference, "in all other respects" here essentially means that the implicit object parameter is reference-compatible (as per [dcl.init.ref]/4) with the (rvalue ignored) argument type.
// [over.match.funcs ]/5.1 special case: rvalue prohibition waived
volatile A a; // argument: a
const volatile A& aref = a; // ok, reference-compatible
// ^^^^^^^^^^^^^^^^^ implicit object parameter
Arguably [over.match.funcs]/5.1 could be clearer on that it applies both for the case where non-const
qualification (usually) prohibits from-rvalue binding, and that volatile
-cv-qualification (usually) prohibits from-rvalue binding.
We may finally query the compiler(s) whether this is actually the particular rule which it uses to allow (ii), by explicitly adding the &
-ref-qualifier, a change that as per [over.match.funcs]/4.1 will have no effect on the type of the implicit object parameter:
#include <memory>
struct A {
void show() const volatile & {}
};
void g(const volatile A &) { }
int main() {
volatile A a;
g(std::move(a)); // (i) Error.
std::move(a).show(); // (ii') Error.
}
As expected, if we add a &
-qualifier to the show()
overload, (ii) likewise fails as (i) did, albeit with another error message (GCC):
error: passing
std::remove_reference<volatile A&>::type
{akavolatile A
} asthis
argument discards qualifiers
For this error, Clang (10.0.0) arguably has a more on-spot error message:
error:
this
argument to member functionshow
is an rvalue, but function has non-const lvalue ref-qualifier
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With