One of the cool new features of the upcoming C++ standard, C++0x, are "rvalue references." An rvalue reference is similar to an lvalue (normal) reference, except that it can be bound to a temporary value (normally, a temporary can only be bound to a const reference):
void FunctionWithLValueRef(int& a) {…}
void FunctionWithRValueRef(int&& a) {…}
int main() {
FunctionWithLValueRef(5); // error, 5 is a temporary
FunctionWithRValueRef(5); // okay
}
So, why did they invent a whole new type, instead of just removing the restrictions on normal references to allow them to be bound to temporaries?
It would be pointless. You would change the thing in the function, and the change would be lost immediately because the thing was actually a temporary.
The reason for the new type stems from the need to be able to decide what actually is an rvalue and what not. Only then you can actually use them for the cool things they are used.
string toupper(string && s) { // for nonconst rvalues
for(char &c : s) make_uppercase(c);
return move(s); // move s into a returned string object
}
string toupper(string const& s) { // for the rest
// calls the rvalue reference version, by passing
// an rvalue copy.
return toupper(string(s));
}
Now, if you have some rvalue and pass it to toupper, the rvalue can directly be modified, because we know the temporary is a throw-away thing anyway, so we can aswell just change it and don't need to copy it. Also, the same observation is used for the thing called move-constructors and move-assignment. The right hand side is not copied, but its things are just stolen away and moved to *this.
If you were to say that rvalues can bind to non-const lvalue references, then you would have no way to figure out whether that references an lvalue (named object) or an rvalue (temporary) in the end.
It's probably more little know, but useful anyway, you can put lvalue or rvalue ref-qualifiers on a member function. Here is an example, which naturally extends the existing semantics of rvalue references to the implicit object parameter:
struct string {
string& operator=(string const& other) & { /* ... */ }
};
Now, you can't anymore say
string() = "hello";
Which is confusing and is not really making sense most of the time. What the & above does is saying that the assignment operator can only be invoked on lvalues. The same can be done for rvalues, by putting &&.
Because adding a new kind of reference allows you to write two overloads of a method:
void CopyFrom(MyClass &&c)
{
dataMember.swap(c);
}
void CopyFrom(const MyClass &c)
{
dataMember.copyTheHardWay(c);
}
The version that accepts the new kind of reference is allowed to modify the variable it receives, because that variable isn't going to be used anywhere else. So it can "steal" the contents of it.
This is the whole reason this feature was added; retaining one type of reference wouldn't achieve the desired goal.
Related
Why is it not allowed to get non-const reference to a temporary object,
which function getx() returns? Clearly, this is prohibited by C++ Standard
but I am interested in the purpose of such restriction, not a reference to the standard.
struct X
{
X& ref() { return *this; }
};
X getx() { return X();}
void g(X & x) {}
int f()
{
const X& x = getx(); // OK
X& x = getx(); // error
X& x = getx().ref(); // OK
g(getx()); //error
g(getx().ref()); //OK
return 0;
}
It is clear that the lifetime of the object cannot be the cause, because
constant reference to an object is not prohibited by C++ Standard.
It is clear that the temporary object is not constant in the sample above, because calls to non-constant functions are permitted. For instance, ref() could modify the temporary object.
In addition, ref() allows you to fool the compiler and get a link to this temporary object and that solves our problem.
In addition:
They say "assigning a temporary object to the const reference extends the lifetime of this object" and " Nothing is said about non-const references though".
My additional question. Does following assignment extend the lifetime of temporary object?
X& x = getx().ref(); // OK
From this Visual C++ blog article about rvalue references:
... C++ doesn't want you to accidentally
modify temporaries, but directly
calling a non-const member function on
a modifiable rvalue is explicit, so
it's allowed ...
Basically, you shouldn't try to modify temporaries for the very reason that they are temporary objects and will die any moment now. The reason you are allowed to call non-const methods is that, well, you are welcome to do some "stupid" things as long as you know what you are doing and you are explicit about it (like, using reinterpret_cast). But if you bind a temporary to a non-const reference, you can keep passing it around "forever" just to have your manipulation of the object disappear, because somewhere along the way you completely forgot this was a temporary.
If I were you, I would rethink the design of my functions. Why is g() accepting reference, does it modify the parameter? If no, make it const reference, if yes, why do you try to pass temporary to it, don't you care it's a temporary you are modifying? Why is getx() returning temporary anyway? If you share with us your real scenario and what you are trying to accomplish, you may get some good suggestions on how to do it.
Going against the language and fooling the compiler rarely solves problems - usually it creates problems.
Edit: Addressing questions in comment:
1) `X& x = getx().ref(); // OK when will x die?` - I don't know and I don't care, because this is exactly what I mean by "going against the language". The language says "temporaries die at the end of the statement, unless they are bound to const reference, in which case they die when the reference goes out of scope". Applying that rule, it seems x is already dead at the beginning of the next statement, since it's not bound to const reference (the compiler doesn't know what ref() returns). This is just a guess however.
I stated the purpose clearly: you are not allowed to modify temporaries, because it just does not make sense (ignoring C++0x rvalue references). The question "then why am I allowed to call non-const members?" is a good one, but I don't have better answer than the one I already stated above.
Well, if I'm right about x in X& x = getx().ref(); dying at the end of the statement, the problems are obvious.
Anyway, based on your question and comments I don't think even these extra answers will satisfy you. Here is a final attempt/summary: The C++ committee decided it doesn't make sense to modify temporaries, therefore, they disallowed binding to non-const references. May be some compiler implementation or historic issues were also involved, I don't know. Then, some specific case emerged, and it was decided that against all odds, they will still allow direct modification through calling non-const method. But that's an exception - you are generally not allowed to modify temporaries. Yes, C++ is often that weird.
In your code getx() returns a temporary object, a so-called "rvalue". You can copy rvalues into objects (aka. variables) or bind them to to const references (which will extend their life-time until the end of the reference's life). You cannot bind rvalues to non-const references.
This was a deliberate design decision in order to prevent users from accidentally modifying an object that is going to die at the end of the expression:
g(getx()); // g() would modify an object without anyone being able to observe
If you want to do this, you will have to either make a local copy or of the object first or bind it to a const reference:
X x1 = getx();
const X& x2 = getx(); // extend lifetime of temporary to lifetime of const reference
g(x1); // fine
g(x2); // can't bind a const reference to a non-const reference
Note that the next C++ standard will include rvalue references. What you know as references is therefore becoming to be called "lvalue references". You will be allowed to bind rvalues to rvalue references and you can overload functions on "rvalue-ness":
void g(X&); // #1, takes an ordinary (lvalue) reference
void g(X&&); // #2, takes an rvalue reference
X x;
g(x); // calls #1
g(getx()); // calls #2
g(X()); // calls #2, too
The idea behind rvalue references is that, since these objects are going to die anyway, you can take advantage of that knowledge and implement what's called "move semantics", a certain kind of optimization:
class X {
X(X&& rhs)
: pimpl( rhs.pimpl ) // steal rhs' data...
{
rhs.pimpl = NULL; // ...and leave it empty, but deconstructible
}
data* pimpl; // you would use a smart ptr, of course
};
X x(getx()); // x will steal the rvalue's data, leaving the temporary object empty
What you are showing is that operator chaining is allowed.
X& x = getx().ref(); // OK
The expression is 'getx().ref();' and this is executed to completion before assignment to 'x'.
Note that getx() does not return a reference but a fully formed object into the local context. The object is temporary but it is not const, thus allowing you to call other methods to compute a value or have other side effects happen.
// It would allow things like this.
getPipeline().procInstr(1).procInstr(2).procInstr(3);
// or more commonly
std::cout << getManiplator() << 5;
Look at the end of this answer for a better example of this
You can not bind a temporary to a reference because doing so will generate a reference to an object that will be destroyed at the end of the expression thus leaving you with a dangling reference (which is untidy and the standard does not like untidy).
The value returned by ref() is a valid reference but the method does not pay any attention to the lifespan of the object it is returning (because it can not have that information within its context). You have basically just done the equivalent of:
x& = const_cast<x&>(getX());
The reason it is OK to do this with a const reference to a temporary object is that the standard extends the lifespan of the temporary to the lifespan of the reference so the temporary objects lifespan is extended beyond the end of the statement.
So the only remaining question is why does the standard not want to allow reference to temporaries to extend the life of the object beyond the end of the statement?
I believe it is because doing so would make the compiler very hard to get correct for temporary objects. It was done for const references to temporaries as this has limited usage and thus forced you to make a copy of the object to do anything useful but does provide some limited functionality.
Think of this situation:
int getI() { return 5;}
int x& = getI();
x++; // Note x is an alias to a variable. What variable are you updating.
Extending the lifespan of this temporary object is going to be very confusing.
While the following:
int const& y = getI();
Will give you code that it is intuitive to use and understand.
If you want to modify the value you should be returning the value to a variable. If you are trying to avoid the cost of copying the obejct back from the function (as it seems that the object is copy constructed back (technically it is)). Then don't bother the compiler is very good at 'Return Value Optimization'
Why is discussed in the C++ FAQ (boldfacing mine):
In C++, non-const references can bind to lvalues and const references can bind to lvalues or rvalues, but there is nothing that can bind to a non-const rvalue. That's to protect people from changing the values of temporaries that are destroyed before their new value can be used. For example:
void incr(int& a) { ++a; }
int i = 0;
incr(i); // i becomes 1
incr(0); // error: 0 is not an lvalue
If that incr(0) were allowed either some temporary that nobody ever saw would be incremented or - far worse - the value of 0 would become 1. The latter sounds silly, but there was actually a bug like that in early Fortran compilers that set aside a memory location to hold the value 0.
The main issue is that
g(getx()); //error
is a logical error: g is modifying the result of getx() but you don't have any chance to examine the modified object. If g didn't need to modify its parameter then it wouldn't have required an lvalue reference, it could have taken the parameter by value or by const reference.
const X& x = getx(); // OK
is valid because you sometimes need to reuse the result of an expression, and it's pretty clear that you're dealing with a temporary object.
However it is not possible to make
X& x = getx(); // error
valid without making g(getx()) valid, which is what the language designers were trying to avoid in the first place.
g(getx().ref()); //OK
is valid because methods only know about the const-ness of the this, they don't know if they are called on an lvalue or on an rvalue.
As always in C++, you have a workaround for this rule but you have to signal the compiler that you know what you're doing by being explicit:
g(const_cast<x&>(getX()));
Seems like the original question as to why this is not allowed has been answered clearly: "because it is most likely an error".
FWIW, I thought I'd show how to it could be done, even though I don't think it's a good technique.
The reason I sometimes want to pass a temporary to a method taking a non-const reference is to intentionally throw away a value returned by-reference that the calling method doesn't care about. Something like this:
// Assuming: void Person::GetNameAndAddr(std::string &name, std::string &addr);
string name;
person.GetNameAndAddr(name, string()); // don't care about addr
As explained in previous answers, that doesn't compile. But this compiles and works correctly (with my compiler):
person.GetNameAndAddr(name,
const_cast<string &>(static_cast<const string &>(string())));
This just shows that you can use casting to lie to the compiler. Obviously, it would be much cleaner to declare and pass an unused automatic variable:
string name;
string unused;
person.GetNameAndAddr(name, unused); // don't care about addr
This technique does introduce an unneeded local variable into the method's scope. If for some reason you want to prevent it from being used later in the method, e.g., to avoid confusion or error, you can hide it in a local block:
string name;
{
string unused;
person.GetNameAndAddr(name, unused); // don't care about addr
}
-- Chris
Why would you ever want X& x = getx();? Just use X x = getx(); and rely on RVO.
The evil workaround involves the 'mutable' keyword. Actually being evil is left as an exercise for the reader. Or see here: http://www.ddj.com/cpp/184403758
Excellent question, and here's my attempt at a more concise answer (since a lot of useful info is in comments and hard to dig out in the noise.)
Any reference bound directly to a temporary will extend its life [12.2.5]. On the other hand, a reference initialized with another reference will not (even if it's ultimately the same temporary). That makes sense (the compiler doesn't know what that reference ultimately refers to).
But this whole idea is extremely confusing. E.g. const X &x = X(); will make the temporary last as long as the x reference, but const X &x = X().ref(); will NOT (who knows what ref() actually returned). In the latter case, the destructor for X gets called at the end of this line. (This is observable with a non-trivial destructor.)
So it seems generally confusing and dangerous (why complicate the rules about object lifetimes?), but presumably there was a need at least for const references, so the standard does set this behavior for them.
[From sbi comment]: Note that the fact that binding it to a const reference enhances a
temporary's lifetimes is an exception that's been added deliberately
(TTBOMK in order to allow manual optimizations). There wasn't an
exception added for non-const references, because binding a temporary
to a non-const reference was seen to most likely be a programmer
error.
All temporaries do persist until the end of the full-expression. To make use of them, however, you need a trick like you have with ref(). That's legal. There doesn't seem to be a good reason for the extra hoop to jump through, except to remind the programmer that something unusual is going on (namely, a reference parameter whose modifications will be quickly lost).
[Another sbi comment] The reason Stroustrup gives (in D&E) for disallowing the binding of
rvalues to non-const references is that, if Alexey's g() would modify
the object (which you'd expect from a function taking a non-const
reference), it would modify an object that's going to die, so nobody
could get at the modified value anyway. He says that this, most
likely, is an error.
"It is clear that the temporary object is not constant in the sample above, because calls
to non-constant functions are permitted. For instance, ref() could modify the temporary
object."
In your example getX() does not return a const X so you are able to call ref() in much the same way as you could call X().ref(). You are returning a non const ref and so can call non const methods, what you can't do is assign the ref to a non const reference.
Along with SadSidos comment this makes your three points incorrect.
I have a scenario I would like to share where I wish I could do what Alexey is asking. In a Maya C++ plugin, I have to do the following shenanigan in order to get a value into a node attribute:
MFnDoubleArrayData myArrayData;
MObject myArrayObj = myArrayData.create(myArray);
MPlug myPlug = myNode.findPlug(attributeName);
myPlug.setValue(myArrayObj);
This is tedious to write, so I wrote the following helper functions:
MPlug operator | (MFnDependencyNode& node, MObject& attribute){
MStatus status;
MPlug returnValue = node.findPlug(attribute, &status);
return returnValue;
}
void operator << (MPlug& plug, MDoubleArray& doubleArray){
MStatus status;
MFnDoubleArrayData doubleArrayData;
MObject doubleArrayObject = doubleArrayData.create(doubleArray, &status);
status = plug.setValue(doubleArrayObject);
}
And now I can write the code from the beginning of the post as:
(myNode | attributeName) << myArray;
The problem is it doesn't compile outside of Visual C++, because it's trying to bind the temporary variable returned from the | operator to the MPlug reference of the << operator. I would like it to be a reference because this code is called many times and I'd rather not have MPlug being copied so much. I only need the temporary object to live until the end of the second function.
Well, this is my scenario. Just thought I'd show an example where one would like to do what Alexey describe. I welcome all critiques and suggestions!
Thanks.
If I keep a constant reference to a non-reference returned value of a function in C++11, where does the reference point in the stack? And is it safe to do so?
string foo() {
std::string foo_ret = "foo string";
return foo_ret;
}
int main() {
const std::string& a = foo();
}
Your code is illegal; non-const lvalue references may not bind to rvalues. There's not really a good reason behind this, it's just a language rule that was introduced very early on in C++'s history.
MSVC used to (maybe still does) allow this binding, I can't comment on how MSVC implements it.
You can bind to other reference types though:
std::string const &a = foo(); // (1)
std::string&& b = foo(); // (2)
In case (2), b binds directly to the return value object, which has its lifetime extended to match b's lifetime. Note: no "move" operation occurs here, it is just binding a reference.
In case (1), conceptually, a temporary of type const std::string is initialized from the return value, and that temporary has its lifetime extended to match a's lifetime. In practice this copy will be elided. your code will behave as if the reference bound directly to the return value.
Generally speaking, you should use value semantics. std::string c = foo(); is the safest option. Because of copy elision, it is not any less efficient than the reference options.
The main danger with the reference option is that if the function were changed to return a reference, then a or b may become a dangling reference.
No it is not safe to do so, and it is an error and will not compile.
You might be looking for R Value references: http://www.cprogramming.com/c++11/rvalue-references-and-move-semantics-in-c++11.html
In my opinion, it is legal to to do.
When you return a string from the function, it returns a Rvalue or temporary object. You can not use normal Lvalue reference to fetch the Rvalue but you can use const Lvalue reference to fetch. When you use const Lvalue reference, the life time of the temporary object is extended to be the same as the reference does.
As for where the reference point to in the memory, I guess it may vary by implementation. But what matters here is that the temporary object is no longer 'temporary', although you can not change it.
Is it legal to have a rvalue refrence of a lvalue reference ?
Considering the following example, is there a difference between the two cases in the final rvalueRef in term of what it refer to or type (or others things)?
Type& ref = GetReference()
Type&& rvalueRef = std::move(ref)
vs
Type value = GetValue()
Type&& rvalueRef = std::move(value)
Both code is correct and 100% legal in C++. There is a difference only in the sense that in the first case a reference is moved (or better say, capable of being moved if appropriate destination is used) and in second case, it is the copy (see the example below for clarification on this).
Note that std::move does NOT actually move the object. It only casts the object into a rvalue reference type. That is all.
Also note that in both cases, rvalueRef is still an lvalue — an object which has a name (i.e an identifier to refer to) is never an rvalue. So if you don't actually move it using std::move (or using explicit cast) again, then there is no difference at all.
Here is a concrete example:
//given function
std::string& GetReference();
std::string GetValue();
//target function
void f(std::string param);
And your code follows:
std::string& ref = GetReference();
std::string&& rvalueRef = std::move(ref);
std::string value = GetValue()
std::string&& rvalueCopy = std::move(value); //named changed
till now NO DIFFERENCE at all.
But if you do this:
f(std::move(rvalueRef)); //actual object returned from GetReference is moved!
f(std::move(rvalueCopy)); //only a copy is moved.
The actual object is moved in the first case, and in the second case, the copy is moved. It doesn't make any difference at all if you do this:
f(std::move(GetReference())); //actual object returned from GetReference is moved!
f(std::move(GetValue())); //only a copy is moved.
So you see, in your code, there is no difference because there is NO ACTUAL MOVE at all. Only cast is there. To have an actual move, there should be an appropriate target type which could invoke move-constructor or move-assignment! In your case, there is NO invocation of either.
Q. Is it legal to have a rvalue refrence of a lvalue reference?
That's not really the question you are asking. The question you are really asking is this:
Q. Is it legal to cast an lvalue reference to an rvalue reference?
The answer is yes. std::move just casts its argument to an rvalue reference, which is always legal. The overall correctness of what you are doing depends on what you then do with the result of std::move. There is nothing wrong with your example code.
Q. Is there a difference between these two cases?
// case 1 // case 2
Type& ref = GetReference(); Type value = GetValue();
f(std::move(ref)); f(std::move(value));
The only difference is that in case 1, ref is a reference to the object returned by GetReference(), but in case 2, value is a copy of the object returned by GetValue().
//old school '98 c++, no C++0x stuff
std::string getPath();
void doSomething()
{
const std::string &path = getPath(); //const reference to an rvalue
... // doSomething with path
}
void doSomething2()
{
const std::string path = getPath(); //regular variable
... // doSomething with path
}
what are the differences between doSomething and doSomething2 and which one is prefferable?
Is it safe to use const reference to returned rvalue in doSomething?
Does doSomething2 create a copy of returned rvalue, is compiler allowed to do rvalue optimization here?
Is it safe to use const reference to returned rvalue in doSomething?
Yes, this is perfectly fine. The language has an specific clause that guarantees that if you bind a reference to an rvalue a temporary is created and the lifetime is extended until the end of the scope where the reference is created.
Does doSomething2 create a copy of returned rvalue, is compiler allowed to do rvalue optimization here?
In both cases the cost is the same, as the compiler will do RVO (return value optimization)
what are the differences between doSomething and doSomething2 and which one is prefferable?
The main difference is that in one case you are giving a name to the real object and in the other case the name is given to a reference. The compiler should generate exactly the same code in both versions.
That being said, I find the use of the const& to be misleading. It gives the impression that the local function has a reference to some object somewhere, but it really has a copy (with no name). To realize that this is a copy the maintainer will have to look and verify the signature of the function that is being called. In the case of the value the behavior is more obvious: the function maintains a copy of whatever is returned by the function (which might be a value or reference).
The second difference is that you are only legally allowed to bind a const reference, which means that the function cannot modify the object. In the case of storing a value, the type can be non-const and the function could modify that object, so the approach is more flexible.
In C++03 the only reason to use the const& trick is in the case where you know that the function returns a value, you know that the type derives from a well known base, and you don't want to name the type [Take a look at ScopeGuard]. In C++11 that use is no longer important, as you can just use auto to let the compiler figure out the type automatically.
(from other comments it seems this might be C++ standards version dependant as to guarentees when you bind a reference to an rvalue about its lifespan - this answer reflects C++ classic)
doSomething has a reference to what?
doSomething2 has a copy of something that may not exists anymore which is fine
lets look at getPath():
in general it will take the return value, copy it to the stack and then copy that to the lhs
it can just assign directly to the lhs
it can return "hello" which will create a string on the stack - this cannot be referenced because it is temporary (the problem with doSomething)
it can return a static const string - in which case it can be completely inlined and assigned to the lhs
The reference version could have a dangling reference if the std::string was allocated inside the function getPath() and destroyed when returning from that function.
Then, it's dangerous usage.
If you had a class holding that string and the class outlives the scope of that const std::string & reference, something as MyClass::getPath(), then, in that case, the reference wouldn't be dangling.
The second version will always work.
Let's take the following method as an example:
void Asset::Load( const std::string& path )
{
// complicated method....
}
General use of this method would be as follows:
Asset exampleAsset;
exampleAsset.Load("image0.png");
Since we know most of the time the Path is a temporary rvalue, does it make sense to add an Rvalue version of this method? And if so, is this a correct implementation;
void Asset::Load( const std::string& path )
{
// complicated method....
}
void Asset::Load( std::string&& path )
{
Load(path); // call the above method
}
Is this a correct approach to writing rvalue versions of methods?
For your particular case, the second overload is useless.
With the original code, which has just one overload for Load, this function is called for lvalues and rvalues.
With the new code, the first overload is called for lvalues and the second is called for rvalues. However, the second overload calls the first one. At the end, the effect of calling one or the other implies that the same operation (whatever the first overload does) will be performed.
Therefore, the effects of the original code and the new code are the same but the first code is just simpler.
Deciding whether a function must take an argument by value, lvalue reference or rvalue reference depends very much on what it does. You should provide an overload taking rvalue references when you want to move the passed argument. There are several good references on move semantincs out there, so I won't cover it here.
Bonus:
To help me make my point consider this simple probe class:
struct probe {
probe(const char* ) { std::cout << "ctr " << std::endl; }
probe(const probe& ) { std::cout << "copy" << std::endl; }
probe(probe&& ) { std::cout << "move" << std::endl; }
};
Now consider this function:
void f(const probe& p) {
probe q(p);
// use q;
}
Calling f("foo"); produces the following output:
ctr
copy
No surprises here: we create a temporary probe passing the const char* "foo". Hence the first output line. Then, this temporary is bound to p and a copy q of p is created inside f. Hence the second output line.
Now, consider taking p by value, that is, change f to:
void f(probe p) {
// use p;
}
The output of f("foo"); is now
ctr
Some will be surprised that in this case: there's no copy! In general, if you take an argument by reference and copy it inside your function, then it's better to take the argument by value. In this case, instead of creating a temporary and copying it, the compiler can construct the argument (p in this case) direct from the input ("foo"). For more information, see Want Speed? Pass by Value. by Dave Abrahams.
There are two notable exceptions to this guideline: constructors and assignment operators.
Consider this class:
struct foo {
probe p;
foo(const probe& q) : p(q) { }
};
The constructor takes a probe by const reference and then copy it to p. In this case, following the guideline above doesn't bring any performance improvement and probe's copy constructor will be called anyway. However, taking q by value might create an overload resolution issue similar to the one with assignment operator that I shall cover now.
Suppose that our class probe has a non-throwing swap method. Then the suggested implementation of its assignment operator (thinking in C++03 terms for the time being) is
probe& operator =(const probe& other) {
probe tmp(other);
swap(tmp);
return *this;
}
Then, according to the guideline above, it's better to write it like this
probe& operator =(probe tmp) {
swap(tmp);
return *this;
}
Now enter C++11 with rvalue references and move semantics. You decided to add a move assignment operator:
probe& operator =(probe&&);
Now calling the assignment operator on a temporary creates an ambiguity because both overloads are viable and none is preferred over the other. To resolve this issue, use the original implementation of the assignment operator (taking the argument by const reference).
Actually, this issue is not particular to constructors and assignment operators and might happen with any function. (It's more likely that you will experience it with constructors and assignment operators though.) For instance, calling g("foo"); when g has the following two overloads raises the ambiguity:
void g(probe);
void g(probe&&);
Unless you're doing something other than calling the lvalue reference version of Load, you don't need the second function, as an rvalue will bind to a const lvalue reference.
Since we know most of the time the Path is a temporary rvalue, does it make sense to add an Rvalue version of this method?
Probably not... Unless you need to do something tricky inside Load() that requires a non-const parameter. For example, maybe you want to std::move(Path) into another thread. In that case it might make sense to use move semantics.
Is this a correct approach to writing rvalue versions of methods?
No, you should do it the other way around:
void Asset::load( const std::string& path )
{
auto path_copy = path;
load(std::move(path_copy)); // call the below method
}
void Asset::load( std::string&& path )
{
// complicated method....
}
It's generally a question of whether internally you will make a copy (explicitly, or implicitly) of the incoming object (provide T&& argument), or you will just use it (stick to [const] T&).
If your Load member function doesn't assign from the incoming string, you should simply provide void Asset::Load(const std::string& Path).
If you do assign from the incoming path, say to a member variable, then there's a scenario where it could be slightly more efficient to provide void Asset::Load(std::string&& Path) too, but you'd need a different implementation that assigns ala loaded_from_path_ = std::move(Path);.
The potential benefit is to the caller, in that with the && version they might receive the free-store region that had been owned by the member variable, avoiding a pessimistic delete[]ion of that buffer inside void Asset::Load(const std::string& Path) and possible re-allocation next time the caller's string is assigned to (assuming the buffer's large enough to fit its next value too).
In your stated scenario, you're usually passing in string literals; such caller's will get no benefit from any && overload as there's no caller-owned std::string instance to receive the existing data member's buffer.
Here's what I do when trying to decide on the function signature
(const std::string& const_lvalue) argument is read only
(std::string& lvalue) I can modify argument (usually put something in) so the change would be VISIBLE to the caller
(std::string&& rvalue) I can modify argument (usually steal something from), zero consequences since the caller would no longer see/use this argument (consider it self destroyed after function returns) RVALUE reference bind to a temp object
All three of them are "pass-by-reference", but they show different intentions. 2+3 are similar, they can both modify the argument but 2 wants the modification to be seen by the caller whereas 3 doesn't.
// (2) caller sees the change argument
void ModifyInPlace(Foo& lvalue){
delete lvalue.data_pointer;
lvalue.data_pointer = nullptr;
}
// (3) move constructor, caller ignores the change to the argument
Foo(Foo&& rvalue)
{
this->data_pointer = that.data_pointer;
that.data_pointer = nullptr;
}