(I asked a variation of this question on comp.std.c++ but didn't get an answer.)
Why does the call to f(arg)
in this code call the const ref overload of f
?
void f(const std::string &); //less efficient
void f(std::string &&); //more efficient
void g(const char * arg)
{
f(arg);
}
My intuition says that the f(string &&)
overload should be chosen, because arg
needs to be converted to a temporary no matter what, and the temporary matches the rvalue reference better than the lvalue reference.
This is not what happens in GCC and MSVC (edit: Thanks Sumant: it doesn't happen in GCC 4.3-4.5). In at least G++ and MSVC, any lvalue does not bind to an rvalue reference argument, even if there is an intermediate temporary created. Indeed, if the const ref overload isn't present, the compilers diagnose an error. However, writing f(arg + 0)
or f(std::string(arg))
does choose the rvalue reference overload as you would expect.
From my reading of the C++0x standard, it seems like the implicit conversion of a const char * to a string should be considered when considering if f(string &&)
is viable, just as when passing a const lvalue ref arguments. Section 13.3 (overload resolution) doesn't differentiate between rvalue refs and const references in too many places. Also, it seems that the rule that prevents lvalues from binding to rvalue references (13.3.3.1.4/3) shouldn't apply if there's an intermediate temporary - after all, it's perfectly safe to move from the temporary.
Is this:
EDIT: I have a follow-on question that is related: C++0x rvalue references - lvalues-rvalue binding
“l-value” refers to a memory location that identifies an object. “r-value” refers to the data value that is stored at some address in memory. References in C++ are nothing but the alternative to the already existing variable.
Rvalue references enable you to write one version of a function that accepts arbitrary arguments. Then that function can forward them to another function as if the other function had been called directly.
An lvalue reference can bind to an lvalue, but not to an rvalue.
Typically rvalues are temporary objects that exist on the stack as the result of a function call or other operation. Returning a value from a function will turn that value into an rvalue. Once you call return on an object, the name of the object does not exist anymore (it goes out of scope), so it becomes an rvalue.
GCC is doing it wrong according the FCD. The FCD says at 8.5.3
about reference binding
Your case for the call to the std::string &&
matches none of them, because the initializer is an lvalue. It doesn't get to the place to create a temporary rvalue, because that toplevel bullet already requires an rvalue.
Now, overload resolution doesn't directly use reference binding to see whether there exist an implicit conversion sequence. Instead, it says at 13.3.3.1.4/2
When a parameter of reference type is not bound directly to an argument expression, the conversion sequence is the one required to convert the argument expression to the underlying type of the reference according to 13.3.3.1.
Thus, overload resolution figures out a winner, even though that winner may actually not be able to bind to that argument. For example:
struct B { B(int) { /* ... */ } };
struct A { int bits: 1; };
void f(int&);
void f(B);
int main() { A a; f(a.bits); }
Reference binding at 8.5
forbids bitfields to bind to lvalue references. But overload resolution says that the conversion sequence is the one converting to int
, thus succeeding even though when the call is made later, the call is ill-formed. Thus my bitfields example is ill-formed. If it was to choose the B
version, it would have succeeded, but needed a user defined conversion.
However, there exist two exceptions for that rule. These are
Except for an implicit object parameter, for which see 13.3.1, a standard conversion sequence cannot be formed if it requires binding an lvalue reference to non-const to an rvalue or binding an rvalue reference to an lvalue.
Thus, the following call is valid:
struct B { B(int) { /* ... */ } };
struct A { int bits: 1; };
void f(int&); /* binding an lvalue ref to non-const to rvalue! */
void f(B);
int main() { A a; f(1); }
And thus, your example calls the const T&
version
void f(const std::string &);
void f(std::string &&); // would bind to lvalue!
void g(const char * arg) { f(arg); }
However, if you say f(arg + 0)
, you create an rvalue, and thus the second function is viable.
It was a defect in the standard draft you read. This defect got in as a side effect of some eager editing to disallow binding of rvalue references to lvalues for safety reasons.
Your intuition is right. Of course, there is no harm in allowing an rvalue reference to refer to some unnamed temporary even if the initializer was an lvalue expression. After all, this is what rvalue references are for. The issue you observed has been fixed last year. The upcoming standard will mandate that the second overload will be picked in your example where the rvalue reference will refer to some temporary string object.
The rule fix made it into the draft n3225.pdf (2010-11-27):
- [...]
- Otherwise, the reference shall be an lvalue reference to a non-volatile const type (i.e., cv1 shall be const), or the reference shall be an rvalue reference
and the initializer expression shall be an rvalue or have a function type. [...]
- [...]
- Otherwise, a temporary of [...] is created [...]
double&& rrd3 = i; // rrd3 refers to temporary with value 2.0
But N3225 seems to have missed to say what i
is in this example. The latest draft N3290 contains these examples:
double d2 = 1.0;
double&& rrd2 = d2; // error: copying lvalue of related type
int i3 = 2;
double&& rrd3 = i3; // rrd3 refers to temporary with value 2.0
Since your MSVC version was released before this issue got fixed, it still handles rvalue references according to the old rules. The next MSVC version is expected to implement the new rvalue reference rules (dubbed "rvalue references 2.1" by MSVC developers) see link.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With