Reputation: 7136
I am watching Scott Meyer's video "The Universal Reference/Overloading Collision Conundrum", where he gives an example of what not to do:
class MessedUp {
public:
template<typename T>
void doWork(const T& param) { std::cout << "doWork(const T& param)" << std::endl; }
template<typename T>
void doWork(T&& param) { std::cout << "doWork(T&& param)" << std::endl; }
};
.... //somewhere in the main
MessedUp m;
int w = 10;
const int cw = 20;
m.doWork(cw); // calls doWork(const T& param) as expected
m.doWork(std::move(cw)); // Calls doWork(T&& param)
I am curious as to why compiler chose doWork(T&& param)
rather than doWork(const T& param)
during Template Overload resolution. As far as I know, const
objects can't be moved.
Upvotes: 1
Views: 1041
Reputation: 275415
&&
does not mean move, it means rvalue reference. rvalue references will only bind to temporary(anonymous) objects, or objects cast to appear to be temporary objects by functions like std::move
or std::forward
, or objects automatically marked by the compiler to be temporary like locals returned from function on simple return X;
lines.
You can have an rvalue reference to a const
object. When this happens, you cannot move (unless mutable
state can be moved), but it is still an rvalue reference.
Now, T&&
can bind to an lvalue reference if T
is an lvalue reference, so in type deduction context T&&
can be called a universal reference. So one of the real problems with the above design is that m.doWork(w)
will also bind to T&&
with T=int&
.
In overload resolution, a function that takes a template<typename T> void foo(T&&)
with T=foo&
be considered to be a worse match than template<typename T> void foo(T&)
with T=foo
if everything else is equal: but in your case, there is no T
such that foo(T const&)
is a foo(T const&&)
.
Upvotes: 4
Reputation: 39121
After template type deduction and substitution, the two overloads become:
//template<typename T>
void doWork(const int& param) { std::cout << "doWork(const T& param)" << std::endl; }
//template<typename T>
void doWork(const int&& param) { std::cout << "doWork(T&& param)" << std::endl; }
Note how T
in the second overload has been deduced to const int
.
Now, what happens is normal overload resolution: We compare the implicit conversion sequences required to convert the argument expression std::move(cw)
(which is an xvalue of type const int
) to the parameter types. Both rank as Exact Matches, so we have to look at the tie-breakers in [over.ics.rank]/3 and compare the two implicit conversion sequences S1
and S2
(a reference binding here is a conversion sequence):
Standard conversion sequence
S1
is a better conversion sequence than standard conversion sequenceS2
if [...]
S1
andS2
are reference bindings [...], andS1
binds an rvalue reference to an rvalue andS2
binds an lvalue reference.
As an xvalue is an rvalue (and a glvalue), this point applies, and the second overload is chosen.
Upvotes: 4
Reputation: 4863
What is happening is that doWork(T&& param)
is being called with T = const int
, because that is a perfect match (instead of a conversion to lvalue).
If you had trued to move the object, it would indeed have failed because const objects can't be moved.
Upvotes: 1
Reputation: 56479
&&
are useful to determine temporary rvalues from non-rvalue. So, you can steal the resource safely.
When you use std::move
it casts the type to a rvalue and compiler will uses &&
overload.
Upvotes: 1