rvalue binding confusion in C++
Asked Answered
I

2

8

I have three function calls that I think should be treated (about) the same, but clearly they are not. I'm trying to understand why one of three doesn't compile (g++ -std=c++0x).

// Minimal example to reproduce a compile bug I want to understand.

#include <iostream>
#include <string>

using namespace std;


void bar(const string &&x) { cout << "bar: " << x << endl; }

string returns_a_string() { return string("cow"); }

int main( int argc, char *argv[] )
{
    bar(string("horse"));     // ok
    bar(returns_a_string());  // ok
    string aardvark = "aardvark";
    bar(aardvark);            // not ok, fails to compile, error in next comment
    /*
      rvalue-min.cpp:29:22: error: cannot bind ‘std::string {aka std::basic_string<char>}’ lvalue to ‘const string&& {aka const std::basic_string<char>&&}’
      rvalue-min.cpp:10:6: error:   initializing argument 1 of ‘void barR(const string&&)’
    */
}

This question is a bit along the lines of C++0x rvalue references - lvalues-rvalue binding, but, if it's answered there, my apologies, I wasn't able to distil it out.

What I want is to be able to call my function bar() with any sort of string and have it just work. It's enough to define void barR(const string &x), but I'd really like to understand why.

Thanks much for any help in understanding why the third call is different.

Inapposite answered 13/1, 2012 at 13:57 Comment(1)
Note that you can't really do anything useful with a constant rvalue reference. Typically you'd want to declare bar(std::string &&).Germann
W
19

The purpose of r-value reference parameters is to detect specifically when an object is an r-value. Because if an object is an r-value, then the function knows it won't be used again, so it can do whatever it wants with it. If an l-value could bind to an r-value reference, that would mean the detection I was talking about wasn't actually taking place.

If you want to pass an l-value to one of these functions, you need to use std::move. Passing an object via std::move to a function that takes an r-value reference is like saying, "here, take this object, rip it's guts out, I don't care what happens to it".

For your purposes, the correct answer is to make the parameter const reference. An r-value is perfectly happy being bound to a const reference. Except for move constructors, making r-value reference parameters is almost never the correct thing to do.

Wysocki answered 13/1, 2012 at 14:2 Comment(7)
Doesn't this answer disagree with an example in this article (To find specifically: Ctrl+F "// Line 31"): blogs.msdn.com/b/vcblog/archive/2009/02/03/…Euchromosome
@sftrabbit: Yes it does, but it agrees with the language standard (and also with GCC, which rejects the code in that article).Montes
Thanks. I'd noticed this mistake in the article before but never got round to checking it.Euchromosome
Benjamin Lindley - I see, that makes sense. Thank you for the clear explanation.Inapposite
So in my case, it seems the right thing to do is to define a const reference version and an rvalue version of the function, if my goal is to let people call it either way. Certainly one call can wrap the other. But in terms of design, this seems bad, always being tempted to define both styles. So then perhaps I shouldn't define an rvalue version of the function at all, only a const reference version, and then it's quite clear on usage that bar(returns_a_string()) is wrong and that the solution is to assign to a string before calling bar(). Or am I confused?Inapposite
@jma: I guess you were typing that comment just as I was ammending my answer. Read again.Wysocki
@jma: And no, you don't need to assign to a string before you use it with an r-value. A const reference is perfectly happy being bound to an r-value.Wysocki
A
5

You need to use std::move. This works fine :

bar(std::move(aardvark));
Abolition answered 13/1, 2012 at 14:0 Comment(2)
Fair enough, though it imposes work on the client. Good to know, though, thanks.Inapposite
@jma: the client has to do that because the client is giving express permission for the function to rip the guts out of the object. (which is why const is odd.) You should only have (const std::string& rhs) code to take everything.Bookman

© 2022 - 2024 — McMap. All rights reserved.