std::allocator::construct
should use uniform initializationSection: 20.2.10.2 [allocator.members] Status: Resolved Submitter: David Krauss Opened: 2011-10-07 Last modified: 2020-09-06
Priority: 2
View all other issues in [allocator.members].
View all issues with Resolved status.
Discussion:
When the EmplaceConstructible
(23.2.2 [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.
std::allocator<T>::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.
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<int>(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.
The change is quite small because EmplaceConstructible
is defined in terms of whatever allocator is specified,
and there is no need to explicitly mention SFINAE in the normative text.
[2012, Kona]
Move to Open.
There appears to be a real concern with initializing aggregates, that can be performed only using brace-initialization. There is little interest in the rest of the issue, given the existence of 'emplace' methods in C++11.
Move to Open, to find an acceptable solution for intializing aggregates. There is the potential that EWG may have an interest in this area of language consistency as well.
[2013-10-13, Ville]
This issue is related to 2070.
[2015-02 Cologne]
Move to EWG, Ville to write a paper.
[2015-09, Telecon]
Ville: N4462 reviewed in Lenexa. EWG discussion to continue in Kona.
[2016-08 Chicago]
See N4462
The notes in Lenexa say that Marshall & Jonathan volunteered to write a paper on this
[2018-08-23 Batavia Issues processing]
P0960 (currently in flight) should resolve this.
[2020-01 Resolved by the adoption of P0960 in Kona.]
Proposed resolution:
This wording is relative to the FDIS.
Change 20.2.10.2 [allocator.members] p12 as indicated:
template <class U, class... Args> void construct(U* p, Args&&... args);12 Effects:
::new((void *)p) U(std::forward<Args>(args)...)
ifis_constructible<U, Args...>::value
istrue
, else::new((void *)p) U{std::forward<Args>(args)...}