Sign in to follow this  
Servant of the Lord

Am I thinking about r-values correctly?

Recommended Posts

Trying to refresh and lock-in my understanding. If I do this:
std::string DoSomething(std::string &&text)
{
	std::string newText = text;
	
	//...alter 'newText'
	
	return newText;
}

std::string strA;
strB = DoSomething(strA); //Copies 'strA'. 'strA' is not altered.
strC = DoSomething(std::move(strA)); //Moves 'strA' into 'strC', avoiding a copy, but 'strA' is now undefined.
strD = DoSomething("string literal"); //Uses move semantics, avoiding a copy.
Am I correct in thinking an lvalue passed into this function taking an rvalue, would not modify the original variable?

Do I even need to do "std::string newText = text;"? Do rvalue references, passed an lvalue, become lvalue references, or lvalues?
Basically, does rvalue references decay into pass-by-reference, or pass-by-value, when given a non-const lvalue or lvalue reference.

Share this post


Link to post
Share on other sites
Passing an lvalue to a function that only takes an rvalue reference shouldn't even compile. However, your function as written doesn't modify the referred to string. When you refer to an rvalue reference by name it's treated as a lvalue reference (I know, uninituitive). You need to use std::move() inside the function body on the rvalue reference (or otherwise cast it to an rvalue reference) for it be treated as an rvalue reference.

Share this post


Link to post
Share on other sites
SiCrane is right to point out that you need to use std::move inside DoSomething.

The only time an lvalue reference binds an rvalue reference, is in when it's a deduced type, such as a template dependant type, an auto type, or an argument to decltype. Using declarations may also qualify. In these case the rvalue reference behaves as a universal reference, used for perfect forwarding. Whenever an lvalue is passed to such a type, the deduced type is an lvalue reference. In all other cases, the code will not compile.

Also note, the only reason strA cannot be used after it's moved, is because the library part of the standard says so. std::move does not by itself cause that. You could write a class which is safe to move then use. Edited by King Mir

Share this post


Link to post
Share on other sites
So if I want a function that moves if given an rvalue, and copies if given an lvalue, then I just take by value?
#include <iostream>
#include "Common/Assorted/ConstructionCounter.h"

ConstructionCounter DoSomething(ConstructionCounter instance)
{
	return instance;
}

int main(int argc, char *argv[])
{
	{
		ConstructionCounter instanceA; //One construct, one destruct.
		ConstructionCounter instanceB = DoSomething(instanceA); //One copy, one move, two destructs.
		ConstructionCounter instanceC = DoSomething(std::move(instanceA)); //Two moves, two destructs.
		ConstructionCounter instanceD = DoSomething(ConstructionCounter()); //One construct, one move, two destructs
	}
	
	std::cout << "Constructions: " << ConstructionCounter::ConstructorCount << std::endl;
	std::cout << "Destructions: " << ConstructionCounter::DestructorCount << std::endl;
	std::cout << "Copy Constructs: " << ConstructionCounter::CopyCount << std::endl;
	std::cout << "Move Constructs: " << ConstructionCounter::MoveCount << std::endl;
	
	return 0;
}

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this