scoped_allocator_adaptor::construct()
Section: 20.5.4 [allocator.adaptor.members], 20.2.8.2 [allocator.uses.construction] Status: C++17 Submitter: Jonathan Wakely Opened: 2016-01-15 Last modified: 2017-07-30
Priority: 0
View all other issues in [allocator.adaptor.members].
View all issues with C++17 status.
Discussion:
20.5.4 [allocator.adaptor.members] p9 says that the is_constructible
tests are done using inner_allocator_type
, which checks for
construction from an rvalue, but then the constructor is passed
inner_allocator()
which returns a non-const
lvalue reference. The
value categories should be consistent, otherwise this fails to
compile:
#include <memory> #include <scoped_allocator> struct X { using allocator_type = std::allocator<X>; X(std::allocator_arg_t, allocator_type&&) { } X(allocator_type&) { } }; int main() { std::scoped_allocator_adaptor<std::allocator<X>> sa; sa.construct(sa.allocate(1)); }
uses_allocator<X, decltype(sa)::inner_allocator_type>>
is true, because
it can be constructed from an rvalue of the allocator type, so bullet (9.1) doesn't apply.
is_constructible<X, allocator_arg_t, decltype(sa)::inner_allocator_type>
is true, so bullet (9.2) applies.
That means we try to construct the object passing it
sa.inner_allocator()
which is an lvalue reference, so it fails.
The is_constructible
checks should use an lvalue reference, as that's
what's actually going to be used.
I don't think the same problem exists in the related wording in
20.2.8.2 [allocator.uses.construction] if we assume that the value categories
of v1, v2, ..., vN
and alloc
are meant to be preserved, so that the
is_constructible
traits and the initialization expressions match.
However, it does say "an allocator alloc
of type Alloc
" and if Alloc
is an reference type then it's not an allocator, so I suggest a small tweak there too.
[2016-02, Issues Telecon]
Strike first paragraph of PR, and move to Tentatively Ready.
Original Resolution [SUPERSEDED]:
Change 20.2.8.2 [allocator.uses.construction] p1:
-1- Uses-allocator construction with allocator
Alloc
refers to the construction of an objectobj
of typeT
, using constructor argumentsv1, v2, ..., vN
of typesV1, V2, ..., VN
, respectively, and an allocator (or reference to an allocator)alloc
of typeAlloc
, according to the following rules:Change the 2nd and 3rd bullets in 20.5.4 [allocator.adaptor.members] p9 to add two lvalue-references:
(9.2) — Otherwise, if
uses_allocator<T, inner_allocator_type>::value
istrue
andis_constructible<T, allocator_arg_t, inner_allocator_type&, Args...>::value
istrue
, callsOUTERMOST_ALLOC_TRAITS(*this)::construct(OUTERMOST(*this), p, allocator_arg, inner_allocator(), std::forward<Args>(args)...)
.(9.3) — Otherwise, if
uses_allocator<T, inner_allocator_type>::value
istrue
andis_constructible<T, Args..., inner_allocator_type&>::value
istrue
, callsOUTERMOST_ALLOC_TRAITS(*this)::construct(OUTERMOST(*this), p, std::forward<Args>(args)..., inner_allocator())
.Change the 2nd, 3rd, 6th, and 7th bullets in 20.5.4 [allocator.adaptor.members] p11 to add four lvalue-references:
(11.2) — Otherwise, if
uses_allocator<T1, inner_allocator_type>::value
istrue
andis_constructible<T1, allocator_arg_t, inner_allocator_type&, Args1...>::value
istrue
, thenxprime
istuple_cat(tuple<allocator_arg_t, inner_allocator_type&>(allocator_arg, inner_allocator()), std::move(x))
.(11.3) — Otherwise, if
uses_allocator<T1, inner_allocator_type>::value
istrue
andis_constructible<T1, Args1..., inner_allocator_type&>::value
istrue
, thenxprime
istuple_cat(std::move(x), tuple<inner_allocator_type&>(inner_allocator()))
.[…]
(11.6) — Otherwise, if
uses_allocator<T2, inner_allocator_type>::value
istrue
andis_constructible<T2, allocator_arg_t, inner_allocator_type&, Args2...>::value
istrue
, thenyprime
istuple_cat(tuple<allocator_arg_t, inner_allocator_type&>(allocator_arg, inner_allocator()), std::move(y))
.(11.7) — Otherwise, if
uses_allocator<T2, inner_allocator_type>::value
istrue
andis_constructible<T2, Args2..., inner_allocator_type&>::value
istrue
, thenyprime
istuple_cat(std::move(y), tuple<inner_allocator_type&>(inner_allocator()))
.
[2016-02, Issues Telecon]
P0; move to Tentatively Ready.
Proposed resolution:
This wording is relative to N4567.
Change the 2nd and 3rd bullets in 20.5.4 [allocator.adaptor.members] p9 to add two lvalue-references:
(9.2) — Otherwise, if
uses_allocator<T, inner_allocator_type>::value
istrue
andis_constructible<T, allocator_arg_t, inner_allocator_type&, Args...>::value
istrue
, callsOUTERMOST_ALLOC_TRAITS(*this)::construct(OUTERMOST(*this), p, allocator_arg, inner_allocator(), std::forward<Args>(args)...)
.(9.3) — Otherwise, if
uses_allocator<T, inner_allocator_type>::value
istrue
andis_constructible<T, Args..., inner_allocator_type&>::value
istrue
, callsOUTERMOST_ALLOC_TRAITS(*this)::construct(OUTERMOST(*this), p, std::forward<Args>(args)..., inner_allocator())
.
Change the 2nd, 3rd, 6th, and 7th bullets in 20.5.4 [allocator.adaptor.members] p11 to add four lvalue-references:
(11.2) — Otherwise, if
uses_allocator<T1, inner_allocator_type>::value
istrue
andis_constructible<T1, allocator_arg_t, inner_allocator_type&, Args1...>::value
istrue
, thenxprime
istuple_cat(tuple<allocator_arg_t, inner_allocator_type&>(allocator_arg, inner_allocator()), std::move(x))
.(11.3) — Otherwise, if
uses_allocator<T1, inner_allocator_type>::value
istrue
andis_constructible<T1, Args1..., inner_allocator_type&>::value
istrue
, thenxprime
istuple_cat(std::move(x), tuple<inner_allocator_type&>(inner_allocator()))
.[…]
(11.6) — Otherwise, if
uses_allocator<T2, inner_allocator_type>::value
istrue
andis_constructible<T2, allocator_arg_t, inner_allocator_type&, Args2...>::value
istrue
, thenyprime
istuple_cat(tuple<allocator_arg_t, inner_allocator_type&>(allocator_arg, inner_allocator()), std::move(y))
.(11.7) — Otherwise, if
uses_allocator<T2, inner_allocator_type>::value
istrue
andis_constructible<T2, Args2..., inner_allocator_type&>::value
istrue
, thenyprime
istuple_cat(std::move(y), tuple<inner_allocator_type&>(inner_allocator()))
.