Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Copy elision when creating object inside emplace()

I see a lot of code at work where people use emplace and emplace_back with a temporary object, like this:

struct A {
    A::A(int, int);
};

vector<A> v;
vector<A>.emplace_back(A(1, 2));

I know that the whole point of emplace_back is to be able to pass the parameters directly, like this:

v.emplace_back(1, 2);

But unfortunately this is not clear to a few people. But let's not dwell on that....

My question is: is the compiler able to optimize this and skip the create and copy? Or should I really try to fix these occurrences?

For your reference... we're working with C++14.

like image 866
mystery_doctor Avatar asked Jun 07 '18 12:06

mystery_doctor


2 Answers

My question is: is the compiler able to optimize this and skip the create and copy? Or should I really try to fix these occurrences?

It can't avoid a copy, in the general case. Since emplace_back accepts by forwarding references, it must create temporaries from a pure standardese perspective. Those references must bind to objects, after all.

Copy elision is a set of rules that allows a copy(or move) constructor to be avoided, and a copy elided, even if the constructor and corresponding destructor have side-effects. It applies in only specific circumstances. And passing arguments by reference is not one of those. So for non-trivial types, where the object copies can't be inlined by the as-if rule, the compiler's hands are bound if it aims to be standard conformant.

like image 131
StoryTeller - Unslander Monica Avatar answered Oct 21 '22 21:10

StoryTeller - Unslander Monica


The easy answer is no; elision doesn't work with perfect forwarding. But this is c++ so the answer is actually yes.

It requires a touch of boilerplate:

struct A {
  A(int, int){std::cout << "A(int,int)\n"; }
  A(A&&){std::cout<<"A(A&&)\n";}
};

template<class F>
struct maker_t {
  F f;
  template<class T>
  operator T()&&{ return f(); }
};

template<class F>
maker_t<std::decay_t<F>> maker( F&& f ) { return {std::forward<F>(f)}; }

vector<A> v;
v.emplace_back(maker([]{ return A(1,2); }));

live example.

Output is one call to A(int,int). No move occurs. In c++17 the making doesn't even require that a move constructor exist (but the vector does, as it thinks it may have to move the elements in an already allocated buffer). In c++14 the moves are simply elided.

like image 25
Yakk - Adam Nevraumont Avatar answered Oct 21 '22 22:10

Yakk - Adam Nevraumont