newprint
newprint

Reputation: 7136

Moving const and overloaded Universal Reference in C++

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

Answers (4)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

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

dyp
dyp

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 sequence S2 if [...]

  • S1 and S2 are reference bindings [...], and S1 binds an rvalue reference to an rvalue and S2 binds an lvalue reference.

As an xvalue is an rvalue (and a glvalue), this point applies, and the second overload is chosen.

Upvotes: 4

Nevin
Nevin

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

masoud
masoud

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

Related Questions