Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing rvalue references as parameters in function overloads

Tags:

c++

c++11

c++17

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.

like image 555
Alberto Miola Avatar asked Jul 09 '18 20:07

Alberto Miola


Video Answer


2 Answers

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&.
like image 117
lubgr Avatar answered Sep 29 '22 03:09

lubgr


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.

like image 28
hoffmale Avatar answered Sep 29 '22 02:09

hoffmale