Why doesn’t emplace_back() use uniform initialization?

Great minds think alike ;v) . I submitted a defect report and suggested a change to the standard on this very topic.

http://cplusplus.github.com/LWG/lwg-active.html#2089

Also, Luc Danton helped me understand the difficulty: Direct vs uniform initialization in std::allocator.

When the EmplaceConstructible (23.2.1
[container.requirements.general]/13) requirement is used to initialize
an object, direct-initialization occurs. Initializing an aggregate or
using a std::initializer_list constructor with emplace requires naming
the initialized type and moving a temporary. This is a result of
std::allocator::construct using direct-initialization, not
list-initialization (sometimes called “uniform initialization”)
syntax.

Altering std::allocator::construct to use list-initialization
would, among other things, give preference to std::initializer_list
constructor overloads, breaking valid code in an unintuitive and
unfixable way — there would be no way for emplace_back to access a
constructor preempted by std::initializer_list without essentially
reimplementing push_back.

std::vector<std::vector<int>> v;
v.emplace_back(3, 4); // v[0] == {4, 4, 4}, not {3, 4} as in list-initialization

The proposed compromise is to use SFINAE with std::is_constructible,
which tests whether direct-initialization is well formed. If
is_constructible is false, then an alternative
std::allocator::construct overload is chosen which uses
list-initialization. Since list-initialization always falls back on
direct-initialization, the user will see diagnostic messages as if
list-initialization (uniform-initialization) were always being used,
because the direct-initialization overload cannot fail.

I can see two corner cases that expose gaps in this scheme. One occurs
when arguments intended for std::initializer_list satisfy a
constructor, such as trying to emplace-insert a value of {3, 4} in the
above example. The workaround is to explicitly specify the
std::initializer_list type, as in
v.emplace_back(std::initializer_list(3, 4)). Since this matches
the semantics as if std::initializer_list were deduced, there seems to
be no real problem here.

The other case is when arguments intended for aggregate initialization
satisfy a constructor. Since aggregates cannot have user-defined
constructors, this requires that the first nonstatic data member of
the aggregate be implicitly convertible from the aggregate type, and
that the initializer list have one element. The workaround is to
supply an initializer for the second member. It remains impossible to
in-place construct an aggregate with only one nonstatic data member by
conversion from a type convertible to the aggregate’s own type. This
seems like an acceptably small hole.

Leave a Comment