Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should all C++ functions be declared taking rvalue from now?

Tags:

c++

c++11

Why should I or shouldn't I create all my functions and members functions to take a rvalue and leave out versions that take a lvalue? You can always forward lvalue to rvalue, right? I can even have const rvalue, so why is this a bad idea, or a good idea?

What I mean in code is below. The "&&" rvalue reference allows the users to use temporaries and can still use lvalue by a simple forward. So with this in mind, why should I provide print_string(string& str) (lvalue reference) of any function in c++11 (besides const reference, because rvalue there is fine)?

#include <iostream>
#include <string>
#include <utility>

using namespace std;


void print_string(string&& str)
{
    str += "!!!";
    cout << str << endl;
}
void print_string2(string& str)
{
    str += "???";
    cout << str << endl;
}

int main(int argc, char* argv[])
{

    print_string("hi there");  // works
    print_string(string("hi again")); // works
    string lvalue("lvalue");
    print_string(forward<string>(lvalue)); // works as expected
    //print_string(lvalue);  // compile error

    //print_string2("hi there"); // comile error
    //print_string2(string("hi again")); // compile error
    print_string2(lvalue);


    char a;
    cin >> a;
}

output

hi there!!!
hi again!!!
lvalue!!!
lvalue!!!???

void print_string(string&& str) provides a more flexible use case than void print_string2(string& str) so why shouldn't we use rvalue arguments all the time?

like image 773
stewart99 Avatar asked Dec 26 '13 19:12

stewart99


People also ask

When should a function take rvalue reference?

Rvalue References As Return Types So it is only really applicable if you're returning something like member variables or reference parameters and you're moving them out of place. There is only one function that moves a reference parameter out: std::move .

Do functions return Rvalues?

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.

Can rvalue be modified?

rvalue of User Defined Data type can be modified. But it can be modified in same expression using its own member functions only.

Can rvalue be null?

Reference is a type that doesn't have null value. It's true for any kind of references - rvalues or lvalues. If you want to have null values - use pointers.


1 Answers

As with many features of C++, there is a convention of how to use lvalue and rvalue references.

Another example for conventions are the operators, especially == and !=. You could overload them to return doubles, and copy files for the comparison. But per convention, they return bool and only compare the left and right operand without modifications.


The convention for rvalue references is that they refer to objects whose resources can be "stolen":

  • an lvalue reference implies that the object is owned by someone else
  • an rvalue reference implies that the we can steal the resources from the object

Rvalue references have been introduced to support move semantics, that is, transfer of ownership of resources. Move semantics conventionally implies that an object bound to an rvalue reference can be moved from. Moved-from objects conventionally are assumed to be in a valid, but unknown state. For example:

// returns a string that equals i appended to p
string join(string&& p, int i); // string&& means: I do something to p. Or not.

// append i to p
void append(string& p, int i);


string my_string = "hello, world."; // length is 13

append(my_string, 42); // I know what state my_string is specified to be in now
my_string[12] = "!"; // guaranteed to work

string result = join( std::move(my_string), 42 );
// what state is my_string now in?
char c = my_string[1]; // is this still guaranteed to work?


string const cresult = join("hello, number ", 5);
// fine, I don't care what state the temporary is in -- it's already destroyed

One could think of definitions like:

string join(string&& p, int i)
{  return std::move(p) + std::to_string(i);  }

void append(string& p, int i)
{  p += std::to_string(i);  }

This definition of join really does steal the resource from p, and the Standard really doesn't specify the state of p after the operation std::move(p) + std::to_int(i) (which creates a temporary that steals the resource from p).

For this simple example, you could still use

string result = "hello ";
result = join(std::move(result), 42);

to "replace" append. But moves can also be expensive (they always also copy something), and multiple lvalue reference parameters cannot easily be replaced by this technique.

N.B. in my opinion, you should indicate in the name of a function when it takes an lvalue reference and modifies the argument. I certainly don't expect a function named print_string2 to modify the string I pass in.


IMHO, it's the convention that the moved-from object is in a valid, but unspecified state that should keep you from using rvalue references everywhere.

Additionally, there can be a slight performance impact when using too many moves as opposed to in-place manipulation à la join.

like image 126
dyp Avatar answered Nov 03 '22 09:11

dyp