1

In the following code, I am trying to use aggregate initialization with emplace_back. The catch here is that emplace_back takes the constructor arguments and constructs the object directly in the vector, and I want to know whether the copy constructor is called on a A{1,2,3} or not. However, for aggregate initialization to work, the struct should not have a user-defined constructor. Any ideas what's happening behind the scenes?

#include <iostream>
#include <vector>

struct A {
    int x, y, z;
    /*A(int x, int y, int z) : x(x), y(y), z(z) 
    {
        std::cout << "ctor called\n";
    }
    A(const A& other) : x(other.x), y(other.y), z(other.z) {
        std::cout << "copy ctor called\n";
    }*/
};

int main() {

    std::vector<A> vec;

    vec.emplace_back(A{1, 2, 3});

    return 0;
}
  • 1
    The move constructor is called. If you do `.emplace_back(1, 2, 3)`, then it won't be called, but that requires C++20. – HolyBlackCat Aug 18 '23 at 11:39
  • Before C++20, `emplace_XXX` does not work with agreggate initialization. The issue is that emplace_XXX` has to use exactly one of `T object{args...}` or `T object(args...)`, one cannot easily templatize this syntax. C++20 made `()` behave as `{}` for aggregates. – Quimby Aug 18 '23 at 11:39
  • 1
    `A{1, 2, 3}` is aggregate initialization but then you are passing an `A` to `emplace_back` – 463035818_is_not_an_ai Aug 18 '23 at 11:40
  • Aggregate initialization for `std::vector::emplace_back` requires `C++20` https://godbolt.org/z/doGK3EEsz – Marek R Aug 18 '23 at 11:49

1 Answers1

4

emplace_back will construct the object directly in the vector, as you've said. However, you've already constructed a temporary object A{1, 2, 3} at the call site, so that object in the vector will be copy/move constructed, and there are two objects in total. Note that:

  • aggregate types don't have a constructor that initializes their members to a list of parameter, but they have implicitly-defined copy/move constructors if possible
  • A{1, 2, 3} isn't calling any constructor; it performs aggregate initialization

This expression A{1, 2, 3} is a prvalue, and will be passed by rvalue reference to emplace_back. Then, the implicitly-defined move constructor of A is going to be called, not the copy constructor.

Providing a copy constructor undeclares the move constructor

If you uncomment the copy constructor in your code, then the copy constructor is called because the move constructor is not declared. You would have to add:

A(A&&) = default;

Then, the move constructor will be called, not the copy constructor. Furthermore, defining this constructor makes the type non-aggregate, so you now need to define a constructor A(int, int, int) because aggregate initialization is no longer possible.

Note on C++20

In C++20, you can initialize aggregates using parentheses as well. This allows you to use emplace_back as if A had a constructor A(int, int, int):

struct A {
    int x, y, z;
};

std::vector<A> vec;
vec.emplace_back(1, 2, 3);
Jan Schultke
  • 17,446
  • 6
  • 47
  • 96