1
votes
#include <iostream>
using namespace std;

class foo {
public:
    foo() {}
    foo(const foo& other) {cout << "foo's copy constructor " << endl;}
    foo(foo&& other) {cout << "foo's move constructor " << endl;}
    foo(const foo&& other) {cout << "foo's move constructor with const ref ref" << endl;}
};

class bar {
public:
    bar(const foo& f) : f_(move(f)) {}
    foo f_;
};

int main()
{
    foo f;
    bar b(f);
}

In the above code, in absence of the move constructor -- foo(const foo&& other), the rvalue reference move(f) is implicitly converted into a const reference and thus foo's copy constructor is called.

My question is why a const rvalue reference implicitly converted into a const reference instead of a compiler error? Specifically, if I comment out the last move constructor, then foo's copy constructor is called.

1
To clarify, are you asking why the code in the question does not give an error? If you actually mean to ask about code without one of the constructors shown, then make that edit to the code. - M.M
An rvalue reference is still a reference, so it can bind to a const lvalue reference. But a const rvalue reference makes no sense, as a const object can't be moved from. - Remy Lebeau
@M.M My question is about implicit conversion from const ref ref to const ref and why is it (the conversion) allowed instead of a compiler error. In my code, if the last move constructor is commented out, foo's copy constructor is called. The copy constructor is called because of implicit conversion from const ref ref to const ref. I was expecting a warning or an error and was not aware of such implicit conversion. So I would like to find out if there are any reasons for allowing such implicit conversion in C++. - bigdata2
@bigdata2 there's no implicit conversion here, the reference parameter binds directly to the argument - M.M

1 Answers

1
votes

Some simpler code with the same reference binding:

int main()
{
    const foo f;
    const foo&  g = std::move(f);
    const foo&& h = std::move(f);
}

The expression std::move(f) is best described as an xvalue of type const foo.

So we have a reference being initialized by an xvalue of type const foo.

Both of g and h are legal and the reference binds directly. const foo&& can only bind to an rvalue, but const foo& can bind to both lvalues and rvalues. (An xvalue is an rvalue).

There is no implicit conversion as suggested in the title, the reference binds directly to the object designated by the initializer.


In the posted code there is overload resolution between two constructors with the same binding as g and h in my example. Overload resolution selects parameter type const foo&& as a better match than const foo& to an argument of type const foo and category "xvalue", since there is a ranking rule that rvalue arguments prefer rvalue references if all else is equal.

But if you remove either of those two constructors then the other one will be selected.