2586. Wrong value category used in 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]:
  1. Change 20.2.8.2 [allocator.uses.construction] p1:

    -1- Uses-allocator construction with allocator Alloc refers to the construction of an object obj of type T, using constructor arguments v1, v2, ..., vN of types V1, V2, ..., VN, respectively, and an allocator (or reference to an allocator) alloc of type Alloc, according to the following rules:

  2. Change the 2nd and 3rd bullets in 20.5.4 [allocator.adaptor.members] p9 to add two lvalue-references:

    1. (9.2) — Otherwise, if uses_allocator<T, inner_allocator_type>::value is true and is_constructible<T, allocator_arg_t, inner_allocator_type&, Args...>::value is true, calls OUTERMOST_ALLOC_TRAITS(*this)::construct(OUTERMOST(*this), p, allocator_arg, inner_allocator(), std::forward<Args>(args)...).

    2. (9.3) — Otherwise, if uses_allocator<T, inner_allocator_type>::value is true and is_constructible<T, Args..., inner_allocator_type&>::value is true, calls OUTERMOST_ALLOC_TRAITS(*this)::construct(OUTERMOST(*this), p, std::forward<Args>(args)..., inner_allocator()).

  3. Change the 2nd, 3rd, 6th, and 7th bullets in 20.5.4 [allocator.adaptor.members] p11 to add four lvalue-references:

    1. (11.2) — Otherwise, if uses_allocator<T1, inner_allocator_type>::value is true and is_constructible<T1, allocator_arg_t, inner_allocator_type&, Args1...>::value is true, then xprime is tuple_cat(tuple<allocator_arg_t, inner_allocator_type&>(allocator_arg, inner_allocator()), std::move(x)).

    2. (11.3) — Otherwise, if uses_allocator<T1, inner_allocator_type>::value is true and is_constructible<T1, Args1..., inner_allocator_type&>::value is true, then xprime is tuple_cat(std::move(x), tuple<inner_allocator_type&>(inner_allocator())).

    3. […]

    4. (11.6) — Otherwise, if uses_allocator<T2, inner_allocator_type>::value is true and is_constructible<T2, allocator_arg_t, inner_allocator_type&, Args2...>::value is true, then yprime is tuple_cat(tuple<allocator_arg_t, inner_allocator_type&>(allocator_arg, inner_allocator()), std::move(y)).

    5. (11.7) — Otherwise, if uses_allocator<T2, inner_allocator_type>::value is true and is_constructible<T2, Args2..., inner_allocator_type&>::value is true, then yprime is tuple_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.

  1. Change the 2nd and 3rd bullets in 20.5.4 [allocator.adaptor.members] p9 to add two lvalue-references:

    1. (9.2) — Otherwise, if uses_allocator<T, inner_allocator_type>::value is true and is_constructible<T, allocator_arg_t, inner_allocator_type&, Args...>::value is true, calls OUTERMOST_ALLOC_TRAITS(*this)::construct(OUTERMOST(*this), p, allocator_arg, inner_allocator(), std::forward<Args>(args)...).

    2. (9.3) — Otherwise, if uses_allocator<T, inner_allocator_type>::value is true and is_constructible<T, Args..., inner_allocator_type&>::value is true, calls OUTERMOST_ALLOC_TRAITS(*this)::construct(OUTERMOST(*this), p, std::forward<Args>(args)..., inner_allocator()).

  2. Change the 2nd, 3rd, 6th, and 7th bullets in 20.5.4 [allocator.adaptor.members] p11 to add four lvalue-references:

    1. (11.2) — Otherwise, if uses_allocator<T1, inner_allocator_type>::value is true and is_constructible<T1, allocator_arg_t, inner_allocator_type&, Args1...>::value is true, then xprime is tuple_cat(tuple<allocator_arg_t, inner_allocator_type&>(allocator_arg, inner_allocator()), std::move(x)).

    2. (11.3) — Otherwise, if uses_allocator<T1, inner_allocator_type>::value is true and is_constructible<T1, Args1..., inner_allocator_type&>::value is true, then xprime is tuple_cat(std::move(x), tuple<inner_allocator_type&>(inner_allocator())).

    3. […]

    4. (11.6) — Otherwise, if uses_allocator<T2, inner_allocator_type>::value is true and is_constructible<T2, allocator_arg_t, inner_allocator_type&, Args2...>::value is true, then yprime is tuple_cat(tuple<allocator_arg_t, inner_allocator_type&>(allocator_arg, inner_allocator()), std::move(y)).

    5. (11.7) — Otherwise, if uses_allocator<T2, inner_allocator_type>::value is true and is_constructible<T2, Args2..., inner_allocator_type&>::value is true, then yprime is tuple_cat(std::move(y), tuple<inner_allocator_type&>(inner_allocator())).