C++11 Emplace_Back on Vector<Struct>

C++11 emplace_back on vectorstruct?

For anyone from the future, this behavior will be changed in C++20.

In other words, even though implementation internally will still call T(arg0, arg1, ...) it will be considered as regular T{arg0, arg1, ...} that you would expect.

c++11 emplace_back and push_back syntax with struct

For v.emplace_back({41,42});, see how to use std::vector::emplace_back for vector<vector<int> >?


v.emplace_back(41,42); does not work because of some rules in the standard (some emphasis mine):

Table 101 — Optional sequence container operations

Expression: a.emplace_back(args)

Return type: void

Operational semantics:

Appends an object of type T constructed with std::forward<Args>(args)....


Requires: T shall be EmplaceConstructible into X from args. For vector, T shall also be MoveInsertable into X.

For a type to be EmplaceConstructible,

§ 23.2.1.13

— T is EmplaceConstructible into X from args, for zero or more arguments args, means that the following expression is well-formed:

allocator_traits<A>::construct(m, p, args);

std::allocator_traits::construct() in turn does (if possible) a.construct(p, std::forward<Args>(args)...) (where a is m in the EmplaceConstructible expression).

a.construct() here is std::allocator::construct(), which calls ::new((void *)p) U(std::forward<Args>(args)...). This is what causes the compile error.

U(std::forward<Args>(args)...) (take note of the use of direct initialization) will find a constructor of U which accepts the forwarded arguments. However, in your case, my_pair is an aggregate type, which can only be initialized with braced-initialization syntax (aggregate initialization).


v.emplace_back(my_pair{41,42}); works because it calls either the implicitly generated default copy constructor or move constructor (note that these two may not always be generated). A temporary my_pair is first constructed which goes through the same process as that of v.emplace_back(41,42);, only that the argument is an r-value my_pair.


ADDITIONAL 1:

And why does push_back(T&&) work without addition of the struct's name?

It's because of push_back's signatures. push_back()'s argument isn't deduced, which means by doing push_back({1, 2}), a temporary object with the type of the vector's element type is first created and initialized with {1, 2}. That temporary object will then be the one that is passed to push_back(T&&).


Should I just stick with push_back? Is there any reason to use emplace_back(structname{1,2,3}) instead of push_back({1,2,3}) because it will end up calling push_back(T&&) anyway, and is easier to type?

Basically, emplace* functions is meant to optimize and remove the cost of creating temporaries and copying or move constructing objects when inserting them. However, for the case of aggregate data types where doing something like emplace_back(1, 2, 3) isn't possible, and the only way you could insert them is through creating a temporary then copying or moving, then by all means prefer the leaner syntax, and go for push_back({1,2,3}), where it would basically have the same performance as that of emplace_back(structname{1,2,3}).

C++ how to use emplace_back for user defined structure

The comment by @PiotrSkotnicki:

emplace_back is function template which tries to deduce the types of arguments. an initializer list does not have a type, so deduction fails.

Clarifies the problem.

An alternative way to "fix" this issue is to pass an rvalue of the needed type as argument of the constructor, instead of the initializer list:

EntryDef(ID &&id, FType ft, … ) : mid(std::forward<ID>(id)), ftype(ft), … {}

Called as:

Def a;
a.ent.emplace_back(ID{ 2, 1 }, FType::FD_NONE, …);

Live example HERE.

Emplace an aggregate in std::vector

std::vector::emplace expects an iterator as argument too, because it inserts the element before that iterator's position.

Another problem is that your {i, 0.0, 0.0, 1} initialization doesn't work because it isn't in a context which tells what type it needs to instantiate. The reason there isn't any context is due to emplace and emplace_back member functions having generic parameters.

If you just want to append elements to the vector, use emplace_back.

However, emplace_back depends on the element type having a valid constructor in order to work, as the element is initialized through parentheses. That changed in C++20, which now allows aggregate-initialization through parentheses without the need to define a valid constructor.

So, up until C++17, your example would be changed to:

for (int i = 0; i < num_particles; ++i)
particles.push_back({i, 0.0, 0.0, 1});

And in C++20 and later, you may do this instead:

for (int i = 0; i < num_particles; ++i)
particles.emplace_back(i, 0.0, 0.0, 1);

vector's emplace_back - vector as a constructor argument

You forgot another pair of braces for the std::vector. Also, you need to tell emplace_back() what kind of arguments you pass it, so you need to invoke std::vector's constructor:

vecC.emplace_back(std::vector{ B{ std::make_shared<A>(), 2 } }, false);

Alternatively, don't use emplace_back() and use push_back() instead:

vecC.push_back({{{std::make_shared<A>(), 2}}, false});

std::vector::emplace_back with a POD C++

emplace_back uses parentheses to initialize the value, which means a constructor is required to use it until C++20. C++20 introduced parenthesized initialization of aggregates, so you code becomes valid as is.

Until then you can just do

vec.push_back({1.0f, 2.0f, 3.0f, 4.0f});

Initializing inner struct in emplace_back()

how about this:

v.push_back(my_struct{{{0, 1, 2}, {3, 4, 5}, {6, 7, 8}}});

C++11 - emplace_back between 2 vectors doesn't work

Although the existing answer provides a workaround using std::move that makes your program compile, it must be said that your use of emplace_back seems to be based on a misunderstanding.

The way you describe it ("I was trying to [...] moving the content from a vector to another one using emplace_back()") and the way you use it suggest that you think of emplace_back as a method to move elements into the vector, and of push_back as a method to copy elements into a vector. The code you use to fill the first instance of the vector seems to suggest this as well:

std::vector<obj> v;
for( int i = 0; i < 1000; ++i )
{
v.emplace_back(obj("Jon"));
}

But this is not what the difference between emplace_back and push_back is about.

Firstly, even push_back will move (not copy) the elements into the vector if only it is given an rvalue, and if the element type has a move assignment operator.

Secondly, the real use case of emplace_back is to construct elements in place, i.e. you use it when you want to put objects into a vector that do not exist yet. The arguments of emplace_back are the arguments to the constructor of the object. So your loop above should really look like this:

std::vector<obj> v;
for( int i = 0; i < 1000; ++i )
{
v.emplace_back("Jon"); // <-- just pass the string "Jon" , not obj("Jon")
}

The reason why your existing code works is that obj("Jon") is also a valid argument to the constructor (specifically, to the move constructor). But the main idea of emplace_back is that you need not create the object and then move it in. You don't benefit from that idea when you pass obj("Jon") instead of "Jon" to it.

On the other hand, in your second loop you are dealing with objects that were created before. There is no point in using emplace_back to move objects that exist already. And again, emplace_back applied to an existing object does not mean that the object is moved. It only means that it is created in-place, using the ordinary copy constructor (if that exists). If you want to move it, simply use push_back, applied to the result of std::move:

std::vector<obj> p;
for( int i = 0; i < 1000; ++i )
{
p.push_back(std::move(v[i])); // <-- Use push_back to move existing elements
}

Further notes
1) You can simplify the loop above using C++11 range-based for:

std::vector<obj> p;
for (auto &&obj : v)
p.push_back(std::move(obj));

2) Regardless of whether you use an ordinary for-loop or range-based for, you move the elements one by one, which means that the source vector v will remain as a vector of 1000 empty objects. If you actually want to clear the vector in the process (but still use move semantics to transport the elements to the new vector), you can use the move constructor of the vector itself:

std::vector<obj> p(std::move(v));

This reduces the second loop to just a single line, and it makes sure the source vector is cleared.

Performance of std::vector::emplace_back vs. assignment for POD struct

First of all, two observations:

  1. The index access version is missing a negation of the y argument:

    compare vec.emplace_back(i, -i); vs. vec[i] = Data(i, i);
  2. The times you're printing are microseconds, or "µs" ("ms" usually means milliseconds).

    Given that 1,000,000 iterations take 864 us, one iteration takes 0.8 ns, or just a couple of CPU cycles. Comparing that to 2.8 ns, we're talking a difference of a few cycles per iteration.

Then some high-level analysis:

The reason the emplace_back version takes longer than assignment via index access could be because emplace_back, in addition to creating a new element, needs to grow the vector by 1. Even when there's enough reserved space, growing of the vector involves (1) a check if there's enough space, and (2) an update of the internal vector size field.

Vector index access on the other hand, performs no bounds checking, let alone update any size. It literally does as much as a raw pointer dereference.

The element type, struct Data is very simple. Creating, copying or overwriting it should take negligible time.

Finally, we analyze the generated assembly to know for sure what's really going on:

emplace_back version:

        leaq    8000000(%rax), %r14
xorl %ebp, %ebp
movq %rbx, %r12
movq %rax, 8(%rsp)
jmp .L14
.L73:
movl %ebp, %eax
movd %ebp, %xmm0
addq $1, %rbp
addq $8, %rbx
negl %eax
movd %eax, %xmm5
punpckldq %xmm5, %xmm0
movq %xmm0, -8(%rbx)
cmpq $1000000, %rbp
je .L72
.L14:
movq %r14, %r15
subq %r12, %r15
cmpq %rbx, %r14
jne .L73

Index access version:

        leaq    8000000(%rax), %r13
. . .
pxor %xmm1, %xmm1
movq %rax, %r12
movq %rbp, %rax
.L27:
movdqa .LC3(%rip), %xmm2
movdqa %xmm1, %xmm0
addq $16, %rax
paddq .LC2(%rip), %xmm1
paddq %xmm0, %xmm2
shufps $136, %xmm2, %xmm0
movups %xmm0, -16(%rax)
cmpq %rax, %r13
jne .L27

Conclusion:

  1. Overall the compiler did a good job at inlining and eliminating object copying in both versions.
  2. The second version is vectorized, writing 2 elements per iteration (possibly due to lack of negation of y).
  3. The first version is doing more work - we can see the additional counting (addq $1, %rbp) and checking (cmpq $1000000, %rbp).


Related Topics



Leave a reply



Submit