1
#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.

bigdata2
  • 999
  • 1
  • 11
  • 17
  • 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 Apr 08 '20 at 04:43
  • 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 Apr 08 '20 at 04:44
  • @RemyLebeau https://stackoverflow.com/questions/4938875/do-rvalue-references-to-const-have-any-use – M.M Apr 08 '20 at 04:45
  • @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 Apr 08 '20 at 04:53
  • @bigdata2 there's no implicit conversion here, the reference parameter binds directly to the argument – M.M Apr 08 '20 at 04:54

1 Answers1

1

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.

M.M
  • 138,810
  • 21
  • 208
  • 365
  • Is there a reason to allow const references to bind to xvalues? I understand the reasoning of extending the lifetime of the xvalue as suggested in this answer (https://stackoverflow.com/questions/34240794/understanding-the-warning-binding-r-value-to-l-value-reference) but the lifetime can be extended by binding xvalue to an rvalue reference, so why allow binding it to a const&? – bigdata2 Apr 08 '20 at 05:42
  • @bigdata2 having const lvalue references bind to rvalues has been around since the first C++ standard, the rationale is that if you're not modifying it it doesn't really matter what category it was – M.M Apr 08 '20 at 05:51
  • Does a compiler static cast an xvalue to lvalue reference in order to bind it? What does "bind" mean in this context? – bigdata2 Apr 08 '20 at 15:31
  • "bind" means that the name of the reference will thereafter refer to the object designated by the initializer. All references must be bound on creation . There's no "compiler static cast" or anything . – M.M Apr 08 '20 at 23:59