SPMP
SPMP

Reputation: 1223

Named rvalue references

Please pardon my lack of clarity on this topic. I am trying to create functions for inserting a big class into a vector. In this example, I use vector of ints as the big class.

#include <vector>
#include <iostream>
using namespace std;

vector<vector<int>> vectorOfVectors;

void fn(const vector<int> &v) {
    vectorOfVectors.push_back(v);

}
void fn(vector<int> &&v) {
    vectorOfVectors.push_back(std::move(v));
}

int main() {
    vector<int> a({1});
    const vector<int> b({2});
    fn(std::move(a));
    fn(b);
    cout<<b[0];
}

Obviously, I want no copying to be done when possible. My questions:

  1. Does this code do the right thing?
  2. Is there a better way of doing this?
  3. For the same approach to work with custom classes, do I need to define move constructors?

Upvotes: 5

Views: 1374

Answers (2)

David Hammen
David Hammen

Reputation: 33126

Does this code do the right thing?

Yes. C++11 added std::vector::push_back(T&&) for this very reason.

Is there a better way of doing this?

Your fn(const vector<int> &v) and fn(vector<int> &&v) are both doing the same thing, pushing the argument v onto the end of vectorOfVectors. In lieu of your two fn functions you could have used one function template that uses perfect forwarding.

template<typename T>
void fn(T &&v) {
    vectorOfVectors.push_back(std::forward<T>(v));
}

How this works is thanks to the C++11 reference collapsing rules and std::forward. The template type T becomes vector<int>& in the case that v is an lvalue but vector<int>&& in the case that v is an rvalue. The reference collapsing rules means that vector<int>& && becomes vector<int>& while vector<int>&& && becomes vector<int>&&. This does exactly what you want, calling the version of push_back that does a copy in the case of an lvalue but the version that does a move in the case of an rvalue.

One downside is that this sometimes can lead to interesting diagnostics when you get things wrong. ("Interesting" here means hundreds of lines of inscrutable diagnostic text from either g++ or clang++). Another downside is that templates can result in a case of "converters gone wild".

For the same approach to work with custom classes, do I need to define move constructors?

Not necessarily. You'll get an implicitly declared move constructor if the class doesn't declare a user-defined destructor, copy constructor, copy assignment operator, or move assignment operator. The implicitly declared move constructor will be defined as deleted if the class has non-movable data members or derives from a class that can't be moved or deleted.

To me, that's a bit too much to remember. I don't know if this is a good practice or a bad one, but I've started using Foo(const Foo&)=default, with similar declarations for the other rule of five functions. I'll also qualify the constructors as explicit in many cases to avoid the "converters gone wild" problem.

Upvotes: 8

bames53
bames53

Reputation: 88195

  1. It does avoid copying a.
  2. Yes. Using push_back means you're forced to construct at least two objects while emplace_back and perfect forwarding could do less work.

    template<typename... Ts>
    auto fn(Ts &&...ts)
      -> decltype(vectorOfVectors.emplace_back(std::forward<Ts>(ts)...), void())
    {
        vectorOfVectors.emplace_back(std::forward<Ts>(ts)...);
    }
    

    http://melpon.org/wandbox/permlink/sT65g3sDxHI0ZZhZ

3. As long as you're using push_back, you would need the classes to be move constructible in order to avoid copying. You don't necessarily need to define the move contructor yourself though, if you can get the default definition.

Upvotes: 2

Related Questions