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.

Community
  • 1
  • 1
jma
  • 3,580
  • 6
  • 40
  • 60
  • 3
    Note that you can't really do anything useful with a *constant* rvalue reference. Typically you'd want to declare `bar(std::string &&)`. – Kerrek SB Jan 13 '12 at 14:03

2 Answers2

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.

Benjamin Lindley
  • 101,917
  • 9
  • 204
  • 274
  • Doesn't this answer disagree with an example in this article (To find specifically: Ctrl+F "// Line 31"): http://blogs.msdn.com/b/vcblog/archive/2009/02/03/rvalue-references-c-0x-features-in-vc10-part-2.aspx – Joseph Mansfield Jan 13 '12 at 14:20
  • @sftrabbit: Yes it does, but it agrees with the language standard (and also with GCC, which rejects the code in that article). – Mike Seymour Jan 13 '12 at 14:27
  • Thanks. I'd noticed this mistake in the article before but never got round to checking it. – Joseph Mansfield Jan 13 '12 at 14:33
  • Benjamin Lindley - I see, that makes sense. Thank you for the clear explanation. – jma Jan 13 '12 at 14:34
  • 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? – jma Jan 13 '12 at 14:38
  • @jma: I guess you were typing that comment just as I was ammending my answer. Read again. – Benjamin Lindley Jan 13 '12 at 14:39
  • @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. – Benjamin Lindley Jan 13 '12 at 14:41
5

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

bar(std::move(aardvark));
BЈовић
  • 62,405
  • 41
  • 173
  • 273
  • Fair enough, though it imposes work on the client. Good to know, though, thanks. – jma Jan 13 '12 at 14:32
  • 1
    @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. – Mooing Duck Jan 13 '12 at 16:19