Alberto Miola
Alberto Miola

Reputation: 4751

Implementing rvalue references as parameters in function overloads

I've already asked on code review and software engineering but the topic didn't fit the site, so I'm asking here hoping this is not opinion-based. I am an "old school" C++ developer (I've stopped at C++ 2003) but now I've read a few books on modern C++ 11/17 and I'm rewriting some libraries of mine.

The first thing I've made is adding move constructor/assignment operator where needed ( = classes that already had destructor + copy constructor and copy assignment). Basically I'm using the rule of five.

Most of my functions are declared like

func(const std::string& s);

Which is the common way to pass a reference avoiding a copy. By the way there is also the new move semantic and there's somethig that I wasn't able to find in my books/online. This code:

void fun(std::string& x) {
    x.append(" world"); 
    std::cout << x;
}

int main()
{
    std::string s{"Hello "};
    fun(s);
}

Can also be written as:

void fun(std::string&& x) {
    x.append(" world"); 
    std::cout << x;
}

int main()
{
    std::string s{"Hello "};
    fun(std::move(s));
    //or fun("Hello ");
    // or fun(std::string {"Hello" });
}

My question is: when should I declare functions that accept a paramenter that is a rvalue reference?


I understand the usage of && semantic on constructors and assignment operators but not really on functions. In the example above (first function) I have a std::string& x which cannot be called as fun("Hello "); of course because I should delcare the type as const std::string& x. But now the const doesnt allow me to change the string!

Yes, I could use a const cast but I rarely do casts (and if it's the case, they're dynamic casts). The power of the && is that I avoid copies, I don't have to do something like

std::string x = "...";
fun(x); //void fun(std::string& x) {}

and I can assing temporary values that will be moved. Should I declare functions with rvalue references when possible?

I have a library that I'm rewriting with modern C++ 17 and I have functions like:

//only const-ref
Type1 func(const type2& x);
Type3 function(const type4& x);

I am asking if it's worth rewriting all of them as

//const-ref AND rvalue reference
Type1 func(const type2& x);
Type3 function(const type4& x);

Type1 func(type2&& x);
Type3 function(type4&& x);

I don't want to create too many overloads that may be useless but if an user of my library wanted to use the move operation I should create the && param types. Of course I am not doing this for primitive types (int, double, char...) but for containers or classes. What do you suggest?

I am not sure if the latter scenario (with both versions) would be useful or not.

Upvotes: 5

Views: 224

Answers (3)

lubgr
lubgr

Reputation: 38325

Let me comment on four scenarios in your question and examples.

  • std::string_view with pass-by-value is supposed to replace const std::string& parameters and whenever you can guarantee the necessary preconditions for a safe usage of std::string_view (lifetime, pointee doesn't change), it's a good candidate to start modernizing your function signatures.
  • const T& vs. T&& (where T is not subject to template type deduction) with known usage scenarios. The void fun function that appends to a given, modifiable string, will only makes sense as void fun(std::string&&) if calling code doesn't need the result after the call. In this case, the rvalue-reference signature documents this expectation nicely and is the way to go. But these cases are rather rare in my experience.
  • const T& vs. T&& (again, no type deduction) with unknown usage scenarios. A good reference here is std::vector::push_back, which is overloaded for both rvalue and lvalue references. The push_back operation is assumed to be cheap compared to move-construction a T, that's why the overload makes sense. When a function is assumed to be more expensive than such a move-construction, passing the argument by value is a simplification that can make sense (see also Item 41 in EMC++).
  • const T& vs. T&& when type deduction takes place. Here, use universal references together with std::forward whenever possible and the parameters can't be const qualified. If they aren't modified in the function body, go with const T&.

Upvotes: 4

hoffmale
hoffmale

Reputation: 232

You want to use rvalue references only if:

  • You might retain a copy and you need the extra performance (measure!)

    Example for this would be writing a library type (e.g. std::vector) where performance matters to its users.

  • You want only temporaries to be passed to your function

    Example for this is the move assignment operator: After the assignment, the original objects state will not exist anymore.

Forwarding references (T&& with T deduced) fall under the first option.

Upvotes: 2

SergeyA
SergeyA

Reputation: 62613

Rvalue reference (not to be confused with a forwarding reference!) in function arguments is used when there is a need to move ownership from one object to another.

It is true that it is often done in context of move constructors/assignment operators, but this is not the only case. For example, a function accepting an ownership of std::unique_prt could accept it's argument by an rvalue reference.

Upvotes: 1

Related Questions