16

This may be obvious but I think it is something difficult to me. Given this:

void test(std::string&&) { }

std::string x{"test"};
test(std::move(x)); // ok

This code calls test() with a rvalue reference as parameter so the program compiles as I expect.

Now look at this:

void other_test(const std::string&) { }

std::string x{"test"};
other_test(std::move(x)); // ok???

And here I'm tilted. Why does this version compile? The std::move returns a && type; why then I don't get an error in the second method where I use const&?


I know that

int&& s = 5;
const int& s = 5;

is valid because in both cases I provide something that has not an lvalue, it has no addresses. Are && and const& equivalent? If no, are there differences?

Barry
  • 286,269
  • 29
  • 621
  • 977
Emma Rossignoli
  • 935
  • 7
  • 25
  • I cannot understand why const& and && behave in the same way. What is the difference? I expected that the const& version would fail and not print the text – Emma Rossignoli Aug 30 '18 at 20:10
  • I expect an error! From my understanding a move should return an && type and so only an && parameter should work. Why then a const& parameter works? – Emma Rossignoli Aug 30 '18 at 20:12
  • " Why then a const& parameter works?" because rvalue reference can be converted to const reference. – Slava Aug 30 '18 at 20:14
  • 7
    Is the question "Why can a `const&` bind to an rvalue when I expect it to fail?" – Barry Aug 30 '18 at 20:14
  • 1
    @Barry yes that is the question – Emma Rossignoli Aug 30 '18 at 20:15
  • 2
    I think some clarification is needed. Are `void test(std::string&& a)` and `void test(const std::string& a)` _both_ in scope when you do your tests? – alter_igel Aug 30 '18 at 20:15
  • Your added code will fail to compile when you comment first function and uncomment second – Slava Aug 30 '18 at 20:18

5 Answers5

17

std::move doesn't actually move anything out of it's own. It's just a fancy name for a cast to a T&&. Calling test like this test(std::move(x)); only shows that a T&& is implicitly convertible to a const T&. The compiler sees that test only accepts const T& so it converts the T&& returned from std::move to a const T&, that's all there is to it.

Hatted Rooster
  • 35,759
  • 6
  • 62
  • 122
  • 2
    Ok thank you! Now I'd have another question, Why should I declare the paremeter as && when I have const&? – Emma Rossignoli Aug 30 '18 at 20:18
  • 10
    @EmmaRossignoli Because you can't modify an object through a const reference. The point of of using rvalue references and move semantics is to modify the object by stealing its resources. – Miles Budnek Aug 30 '18 at 20:20
  • @EmmaRossignoli If you declare it as `T&&` then you're saying "`test` should only accept an rvalue-reference". It doesn't say anything or impose any requirements on the caller when it comes to how the caller got that rvalue-ref. – Hatted Rooster Aug 30 '18 at 20:21
  • 2
    @EmmaRossignoli Read this answer https://stackoverflow.com/questions/3106110/what-are-move-semantics/11540204#11540204 and understand it it is all explained there – Slava Aug 30 '18 at 20:21
  • Now I understand that && can be converted to const& but the difference is that a const& cannot be edited (of course due to the const) while the && yes. My doubt is solved thank you all – Emma Rossignoli Aug 30 '18 at 20:23
  • @EmmaRossignoli It's by no means "obvious" – Barry Aug 30 '18 at 20:24
  • @EmmaRossignoli And if we're going to be picky, there's no conversion here at all. It's just that a lvalue reference to const can bind to anything, but an rvalue reference can only bind to an rvalue. – Barry Aug 30 '18 at 20:24
  • @EmmaRossignoli Exactly. A good example is constructors. A `T&&` should always be implicitly convertible to a `const T&` so the copy constructor can act as a backup when the move constructor is disabled/deleted. – Hatted Rooster Aug 30 '18 at 20:25
  • @Barry I think this isn't the time to be picky – Hatted Rooster Aug 30 '18 at 20:25
  • @Barry Is that a chicken joke? – Hatted Rooster Aug 30 '18 at 20:26
  • @Barry Fair enough, I agree with you there. I suppose I worded it incorrectly. What I meant was that information overload in a short time span can be bad. – Hatted Rooster Aug 30 '18 at 20:27
  • I get what SombreroChicken said. I thank you @Barry for the comment but I will read again your sentence in a second moment, now I'm learning this and I need easy terms – Emma Rossignoli Aug 30 '18 at 20:29
  • Also note that you can also pass both lvalue ref/rvalue ref to the const lvalue ref. – sww Apr 05 '22 at 20:58
  • When you said `test` and `test(std::move(x));` in the answer, do you mean `other_test` in the 2nd code snippet of the question? – starriet Sep 30 '22 at 09:47
17

In simple terms:

  • && can bind to non-const rvalues (prvalues and xvalues)
  • const && can bind to rvalues (const and non-const)
  • & can bind to non-const lvalues
  • const & can bind to rvalues (prvalues and xvalues) and lvalues (const and non-const for each). A.k.a. to anything.
bolov
  • 72,283
  • 15
  • 145
  • 224
5

If you want a function to expressly allow const-Lvalue objects, but expressly disallow Rvalue objects, write the function signature like this:

void test(const std::string&) { }
void test(std::string&&) = delete;//Will now be considered when matching signatures

int main() {
    std::string string = "test";
    test(string);//OK
    //test(std::move(string));//Compile Error!
    //test("Test2");//Compile Error!
}
Xirema
  • 19,889
  • 4
  • 32
  • 68
2
test(std::string&& a) {
  something(a) //--> not moved because it has lvalue

Names of variables are lvalues. a is a name of a variable, therefore a is an lvalue expression, and therefore it will not be moved from.

It's unclear what you mean by "has". a is an expression. It is a name of a reference, and references refer to objects. Value categories pertain to expressions, not objects.

test(const std::string& a): a is const lvalue reference and like before I have lvalue and rvalue. And plus more, in this case if I called

std::move(a)

where a is a const& the move works!

If by "works" you mean that it invokes a move constructor or assignment, then no, it does not work because no move construction or assignment has happened.

Community
  • 1
  • 1
eerorika
  • 232,697
  • 12
  • 197
  • 326
2

When you call std::move(x), an rvalue reference to the underlying data, test, will be returned. You are allowed to pass rvalue references as const (and const only!) reference parameters because an rvalue reference is implicitly convertible to a const reference. They are arguably the same thing from the function's point of view (a read only parameter). If you removed the const-qualifier of your parameter, this code would not compile:

void other_test(std::string&) { }
std::string x{"test"};
other_test(std::move(x)); //not okay because
//the function can potentially modify the parameter.

See Bo Qian's youtube video on rvalue vs lvalue.

Gautam
  • 2,597
  • 1
  • 28
  • 51
N. Prone
  • 180
  • 7