This is a follow-up to my previous post
With reference to Non-static member functions
Under
const-, volatile-, and ref-qualified member functions
A non-static member function can be declared with no ref-qualifier,... During overload resolution, non-static cv-qualified member function of class X is treated as follows:
no ref-qualifier: the implicit object parameter has type lvalue reference to cv-qualified X and is additionally allowed to bind rvalue implied object argument
To explore this further, I experimented with the source code provided in the above link, as shown below:
#include <utility>
#include <iostream>
using std::move;
using std::cout;
using std::endl;
struct S {
void f() {cout << "no ref-qualifier: the implicit object parameter has type lvalue reference to cv-qualified S and is additionally allowed to bind rvalue implied object argument\n"; }
//if only the below method signature were to be enabled,
//the invocations using rvalue implicit object would fail
//to compile with the error [-fpermissive]
//void f() & {cout << "lvalue\n"; }
//if only the below method signature were to be enabled,
//the invocation using lvalue implicit object would fail
//to complile with the error [-fpermissive]
//void f() && {cout << "rvalue\n"; }
};
int main (void){
S s;
s.f(); // prints "lvalue"
move(s).f(); // prints "rvalue"
S().f(); // prints "rvalue"
return 0;
}
I have provided relevant comments above each non-static member function overload based on reference qualifier, highlighting the compilation issue that would ensue if only that particular overload were to be enabled, in view of the source code in main().
My question is, what is happening under the hoods in order for the non-ref qualified non-static member function to be able to be agnostic to the implicit type of the object upon which it has been invoked? Does the compiler step in with the appropriate overload?
Appreciate your thoughts.
Const- and ref-qualified member functions do not function differently from non-const/ref member functions. These markings are mostly there to stop programmers from misusing stuff.
If you have a function
void g() const, then it does not suddenly compile to different instructions because you remove theconstfrom it - it just means that the compiler stops checking if you mutatethisin the body.Well, mostly... It gets a bit more complicated by the fact that, by upholding various invariants, these keywords also sometimes allow the programmer (or compiler) to do stuff that would not be allowed in the general case. For example, if I know you have given me an rvalue, then I am allowed to steal its innards instead of needing to copy them.
In any case, to illustrate, I ran your three examples through godbolt.
In the first case
void f()compiled to:You don't have to be able to read all that, just see that when I compiled the two other functions they produced this:
Just so we are clear, except for loading different string literals (
.LC0,.LC1), they are all three identical. And if we add-O3, then it all gets inlined intomainas such:No ref-qualifiers:
With ref-qualifiers:
Which again is identical (except for string literals)
In short: Nothing has to be done to make the non-ref-qualified function accept both arguments - it is the other way around: The compiler stops the ref-qualified functions from accepting arguments that they technically could but are not allowed to take (both to guard against programmers doing stuff they should not do, and in case the function was optimized to do something that would actually break on the general case).
On some level it is like the type system: On the machine level the computer is just moving around bits and bytes, and gives absolutely zero fucks about what type you think any given chunk represents. It is purely the compiler trying to hold you to account and make sure you uphold the invariants that you declared, but once the compiler is happy and emits some machine code then the types are long gone.
Or, since I like examples, you can say that it is like the difference between the VIP and the non-VIP door: The doors actually identical, but the guard (compiler) only lets you enter through the ones that (they think) you have permission to.