C++ treats aggregates differently from non-aggregates. Aggregates are basically plain old C-style structs and arrays. Initialising aggregates is done with braces. In C++11, uniform initialisation is also done with braces.

Aggregate initialisation initialises each member directly. So we can initialise non-movable types like std::lock_guard

struct Agg {
	std::lock_guard<std::mutex> lk;
}
 
std::mutex m;
Agg a = { std::lock_guard(m); } // OK

For backwards compatibility with C, aggregate initialisation will value initialise any trailing data members.

struct Foo {
	int a;
	int b;
	int c;
}
 
Foo f = {1}; // b and c are value-initialized (to 0)

Zero-argument initialisation

For a type T, T{} can do uniform initialisation or aggregate initialisation. This will depend on whether T is an aggregate or not.

Also T() can do value-initialisation as a special case.

emplace_back

Emplace back with Args&&... args constructs using a placement-new expression like ::new (p) T(std::forward<Args>(args)...). Note that this is using parens, not braces.

std::vector<std::array<int, 3>> vec_arr; // vector of aggregate
vec_arr.emplace_back(1, 2, 3); // error in C++17

So, the above code fails to compile as std::array<int, 3>(1, 2, 3) fails because std::array has no constructor taking three ints.

C++20 Parens-init for aggregate

In C++20, initialising aggregates is allowed from a parenthesised list of values.