Emplace an Aggregate in Std::Vector

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);

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.

How to emplace to a std::vector of std::array?

std::array doesn't have any constructors, it's initialization follows aggregate initialization.

You have two options, either use push_back instead of emplace_back with a brace-init-list:

voa.push_back({ 0, 0 });

Or you can use std::array's (implicit) copy and move constructors and construct a temporary std::array:

voa.emplace_back(std::array<int>{0, 0}); // pre C++17
voa.emplace_back(std::array{0, 0}); // C++17

Ultimately both methods should generate the same machine code.


In response to your edit:

std::array<int, 2> a = {0, 0};

is NOT the same as

std::array<int, 2> a = std::initializer_list<int>{0, 0};

{0, 0} is a brace-init-list, which doesn't have a type. This is why you can't use them as an argument to a templated function. See this question for more details.

What's the most efficient way to emplace a new element in a vector?

There are two independent questions related to your problem. The first one is whether an object of type ZeroInitialisedType can be constructed such that some of its members are not zero-initialized. I believe this is not possible according to the aggregate-initialization rules. A simple demo is:

struct X { int a, b, c, d, e, f; };

void f(X* ptr)
{
new (ptr) X{7}; // the same with X(7) and X{.a=7}
}

Here, all members b to f are zero-initialized in f().

You can avoid this zeroing by adding the corresponding constructor; however, the class will no longer be an aggregate:

struct Y
{
Y(int a_) : a(a_) { }
int a, b, c, d, e, f;
};

void f(Y* ptr)
{
new (ptr) Y(7);
}

Live demo: https://godbolt.org/z/3aWooxWK7.

The second question is whether the object can be constructed directly in the vector's buffer. I tried some versions with libstdc++ and only the version with "empty" emplace_back plus the back call did that.

Live demo: https://godbolt.org/z/zavvrTGj6.

I guess the cause is in the possible reallocation, which is in our case accomplished by the _M_realloc_insert call. It seems that those calls with non-empty template argument require the source to be in memory (the address is then passed via RDX). Therefore, the addressed objects need to be first stored to the stack.

The version with empty emplace_back does not have such requirements and succeeded with the direct construction in the vector's buffer. Of course, this analysis is GCC+libstdc++-related only.

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});

Is it possible to emplace a std::array in a container? If it is how? If not, why?

Let me try to clarify a bit. First of, std::array<> is an aggregate. It does not have any user-defined constructors at all.

So it is quasi-movable (technical term is trivially movable), meaning you can construct it from temporary, but it is going to simply copy the underlying array. So what the code ends up doing is copying supplied array into the newly allocated vector element.

You would achieve identical results (but will do slightly less typing) if you replace the emplace_back with push_back like following:

    v.push_back({1,2,3});

And emplace_back(1, 2, 3) doesn't compile because std::array<> does not have any constructors which take more than one argument.

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});


Related Topics



Leave a reply



Submit