2782. scoped_allocator_adaptor constructors must be constrained

Section: 23.13.3 [allocator.adaptor.cnstr] Status: C++17 Submitter: Jonathan Wakely Opened: 2016-10-14 Last modified: 2017-07-30

Priority: 0

View all issues with C++17 status.

Discussion:

The templated constructors of scoped_allocator_adaptor need to be constrained, otherwise uses-allocator construction gives the wrong answer and causes errors for code that should compile.

Consider two incompatible allocator types:

template<class T> struct Alloc1 { ... };
template<class T> struct Alloc2 { ... };
static_assert(!is_convertible_v<Alloc1<int>, Alloc2<int>>);

The unconstrained constructors give this bogus answer:

template<class T> using scoped = scoped_allocator_adaptor<T>;
static_assert(is_convertible_v<scoped<Alloc1<int>>, scoped<Alloc2<int>>>);

This causes uses_allocator to give the wrong answer for any specialization involving incompatible scoped_allocator_adaptors, which makes scoped_allocator_adaptor::construct() take an ill-formed branch e.g.

struct X 
{
  using allocator_type = scoped<Alloc2<int>>;
  X(const allocator_type&);
  X();
};
scoped<Alloc1<int>>{}.construct((X*)0);

This fails to compile, because uses_allocator<X, scoped_allocator_adaptor<Alloc2<int>>> is true, so the allocator is passed to the X constructor, but the conversion fails. The error is outside the immediate context, and so is a hard error.

[2016-11-12, Issaquah]

Sat AM: Priority 0; move to Ready

Billy to open another issue about the confusion with the ctor

Proposed resolution:

This wording is relative to N4606.

  1. Modify 23.13.3 [allocator.adaptor.cnstr] by converting "Requires" elements to "Remarks: shall not participate ..." constraints as shown:

    template <class OuterA2>
      scoped_allocator_adaptor(OuterA2&& outerAlloc,
                               const InnerAllocs&... innerAllocs) noexcept;
    

    -2- Requires: OuterAlloc shall be constructible from OuterA2.

    -3- Effects: Initializes the OuterAlloc base class with std::forward<OuterA2>(outerAlloc) and inner with innerAllocs... (hence recursively initializing each allocator within the adaptor with the corresponding allocator from the argument list).

    -?- Remarks: This constructor shall not participate in overload resolution unless is_constructible_v<OuterAlloc, OuterA2> is true.

    […]

    template <class OuterA2>
      scoped_allocator_adaptor(const scoped_allocator_adaptor<OuterA2,
                               InnerAllocs...>& other) noexcept;
    

    -6- Requires: OuterAlloc shall be constructible from OuterA2.

    -7- Effects: Initializes each allocator within the adaptor with the corresponding allocator from other.

    -?- Remarks: This constructor shall not participate in overload resolution unless is_constructible_v<OuterAlloc, const OuterA2&> is true.

    template <class OuterA2>
      scoped_allocator_adaptor(scoped_allocator_adaptor<OuterA2,
                               InnerAllocs...>&& other) noexcept;
    

    -8- Requires: OuterAlloc shall be constructible from OuterA2.

    -9- Effects: Initializes each allocator within the adaptor with the corresponding allocator rvalue from other.

    -?- Remarks: This constructor shall not participate in overload resolution unless is_constructible_v<OuterAlloc, OuterA2> is true.