3

Passing lvalue reference as an rvalue reference argument does not compile. Compiler could create a temporary object with its copy constructor and pass it as an rvalue, but it does not. Still, it does call the constructor, if types do not match.

I'm interested, why does it works that way? Which design logic is here in the C++ standard, that forces the compiler to treat copy-constructor in a different way?

void do_smth(string && s)
{}

void f(const char * s)
{
  do_smth(s); // passes a temporary string constructed from const char*
}

void g(const string & s)
{
  do_smth(s); // does not compile
}

void h(const string & s)
{
  do_smth(string(s)); // ok again
}

What should I do if I do not want to implement the second signature do_smth(const string &)? Should I use pass-by-value void do_smth(string s) instead?

What are other differences betweeen by-value void do_smth(string s) and rvalue-reference void do_smth(string && s), given the object string has move constructor?

Roman Maltsev
  • 307
  • 1
  • 3
  • 9
  • Yes, pass-by-value would be quite useful here. – bipll Jan 30 '19 at 11:31
  • Related [question](https://stackoverflow.com/questions/51253572/implementing-rvalue-references-as-parameters-in-function-overloads/51253882#51253882). – lubgr Jan 30 '19 at 11:33

2 Answers2

4

This is the whole point of rvalue references. They bind to rvalues, and not to lvalues.

That is how you ensure that the overload you wanted is invoked, when you have both an lvalue version and an rvalue version. For example, a copy constructor vs a move constructor.

It's a feature.

Also, your lvalue is const, which does not match the signature of do_smth even if it did not take an rvalue reference.

If you want do_smth to be able to take either kind of expression, either make it take a const string&, or make it a template and have it take a forwarding reference T&& (which looks like an rvalue reference but kind of isn't).

If you want do_smth to store its own version of the string then have it take a value: you can still avoid a copy if needs be by using std::move at the callsite (or by passing an rvalue, which will trigger the string's own move constructor).

See how all options are available and elegant, due to how the rvalue reference binding rules are made? It all fits together.

Deciding what to allow do_smth to take entirely depends on what "smth" it is going to "do", and only you can say what that is.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • What do you mean "ensure that the overload you wanted is invoked, when you have both an lvalue version and an rvalue version"? If I made `void do_smth(const char* s)` signature as well, the compiler would call it in f() instead of contructing a temporary object, would not it? Why lvalue and rvalue overloads are different in that way, and so, compiler does not call copy constructor to convert lvalue to rvalue automatically, when there is no lvalue version of the signature? – Roman Maltsev Jan 30 '19 at 11:50
  • @RomanMaltsev Sorry, I don't understand what "differences" you're referring to. Yes, if you pass an lvalue `const char*` to a function accepting a `const char*`, that'll work. That's the pass-by-value case. You could also pass it to a function accepting a `const char*&` (i.e. an lvalue reference). You could not pass it to a function accepting a `const char*&&` (i.e. an rvalue reference). – Lightness Races in Orbit Jan 30 '19 at 11:52
  • 1
    I mean, why `string(const char*)` constructor is 'good' to silently convert const char* to a temporary string and pass it as rvalue. but `string(const string &) is 'bad' constructor for a same silent conversion? – Roman Maltsev Jan 30 '19 at 11:57
  • 3
    @RomanMaltsev Technically the reason is because one implicit conversion is allowed. `std::string` has an implicit (i.e. non-explicit) constructor from `const char*`, but copy-construction is never an implicit conversion. Your question whether this makes sense is valid though. – Max Langhof Jan 30 '19 at 12:02
  • 1
    @MaxLanghof What do you mean "copy-construction is never an implicit conversion"? A copy constuctor can be either marked with explicit keyword or not marked. When marked, it also prevents automatic copying in passing-by-value, so the code in g would not compile even when `void do_smth(string s)` signature was used instead of rvalue signature. So, when copying an argument to pass it by value, the copy constructor is called implicitly, just as a conversion constructor. So, copy-constructor should have worked or not worked the same way for string and string&&, if it were about explicit keyword. – Roman Maltsev Jan 30 '19 at 12:35
0

in h you give a non const copy of the const string, so the const disappears contrarily to the g case

of course with void do_smth(string s){...} the copy is made at the call and both const and non const string can be given in argument

bruno
  • 32,421
  • 7
  • 25
  • 37