19

I just learned about guaranteed copy elision in C++17. According to the answer on that question:

When you do return T();, this initializes the return value of the function via a prvalue. Since that function returns T, no temporary is created; the initialization of the prvalue simply directly initializes the return value.

The thing to understand is that, since the return value is a prvalue, it is not an object yet. It is merely an initializer for an object, just like T() is.

So I was wondering, does this work for anything other than:

T f() {return T();}
T t = f();

So I wrote this code with emplace_back to test it:

#include <vector>
#include <iostream>
struct BigObj{
  BigObj() = default;
  BigObj(int) { std::cout << "int ctor called" << std::endl;  }
  BigObj(const BigObj&){
    std::cout << "copy ctor called" << std::endl;
  }
  BigObj(BigObj&&){
    std::cout << "move ctor called" << std::endl;
  }
};
BigObj f(){ return BigObj(2); }
int g(){ return 2; }
int main(){
  std::vector<BigObj> v;
  v.reserve(10);
  std::cout << "emplace_back with rvalue \n";
  v.emplace_back(1+1);
  std::cout << "emplace_back with f()\n";
  v.emplace_back(f());
  std::cout << "emplace_back with g()\n";
  v.emplace_back(g());
}

This is the output I get (with copy elision disabled):

emplace_back with rvalue 
int ctor called
emplace_back with f()
int ctor called
move ctor called
emplace_back with g()
int ctor called

It seems that the move constructor is still called even though a prvalue is passed directly into emplace_back, which I thought could be used to directly construct an object instead of being used to construct a temporary and then moving it.

Is there a more elegant way to avoid the move constructor call with emplace_back other than doing something like what the g() function does?

manlio
  • 18,345
  • 14
  • 76
  • 126
user2108462
  • 855
  • 7
  • 23

1 Answers1

22

It seems that the move constructor is still called even though a prvalue is passed directly into emplace_back

You think you do, but you don't pass it into the function. You give it a prvalue as argument, yes, but what emplace_back accepts is a pack of forwarding references. A reference must refer to an object, so a temporary is materialized, and moved.

The correct way to use emplace_back is to pass it the arguments for initializing the object in place. That way you don't need to move the element type of the vector (though you may need to move/copy the arguments).

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • So when a prvalue is passed into any function that accepts any kind of reference (const reference or rvalue reference), by necessity a temporary is constructed? – user2108462 Apr 10 '18 at 08:49
  • 4
    @user2108462 - Yes. Binding to references is a place where temporary materialization would occur. It must happen so we get the object being referred to. – StoryTeller - Unslander Monica Apr 10 '18 at 08:51
  • 1
    @StoryTeller, ah, reading comprehension fail on my part—the comment itself actually says prvalue. – Jan Hudec Apr 10 '18 at 09:24
  • 1
    Of course, what would be cool is to call `v.emplace_back(f);` i.e. don't immediately execute f(), but pass the factory function into emplace to execute at an appropriate time. To do it generically with std::function would be overkill for a simple object, though. – Gem Taylor Apr 10 '18 at 09:49
  • @GemTaylor - Can't you do something like that with expression templates or an equivalent technique? Maybe? Would be nice to have it available without boiler-plate, however, indeed. – StoryTeller - Unslander Monica Apr 10 '18 at 09:54
  • Indeed. I imagine it would look something like the horror that is c++11 std:::map emplace pair, or perhaps std::thread. – Gem Taylor Apr 10 '18 at 10:01