This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of WP status.

### 3525. `uses_allocator_construction_args` fails to handle types convertible to `pair`

Section: 20.2.8.2 [allocator.uses.construction] Status: WP Submitter: Tim Song Opened: 2021-02-23 Last modified: 2022-02-10

Priority: 3

View other active issues in [allocator.uses.construction].

View all other issues in [allocator.uses.construction].

View all issues with WP status.

Discussion:

As currently specified, the following program is ill-formed (and appears to have been since LWG 2975):

>     struct S {
>       operator std::pair<const int, int>() const {
>         return {};
>       }
>     };

>     void f() {
>       std::pmr::map<int, int> s;
>       s.emplace(S{});
>     }

There's no matching overload for `uses_allocator_construction_args<pair<const int, int>>(alloc, S&&)`, since `S` is not a `pair` and every overload for constructing `pair`s that takes one non-allocator argument expects a `pair` from which template arguments can be deduced.

[2021-02-27 Tim adds PR and comments]

The superseded resolution below attempts to solve this issue by adding two additional overloads of `uses_allocator_construction_args` to handle this case. However, the new overloads forces implicit conversions at the call to `uses_allocator_construction_args`, which requires the result to be consumed within the same full-expression before any temporary created from the conversion is destroyed. This is not the case for the `piecewise_construct` overload of `uses_allocator_construction_args`, which recursively calls `uses_allocator_construction_args` for the two elements of the pair, which might themselves be `pair`s.

The approach taken in the revised PR is to produce an exposition-only `pair-constructor` object instead. The object holds the allocator and the argument by reference, implicitly converts to the specified specialization of `pair`, and when so converted return a `pair` that is constructed by uses-allocator construction with the converted value of the original argument. This maintains the existing design that `pair` itself doesn't know anything about allocator construction.

Previous resolution [SUPERSEDED]:

> This wording is relative to N4878.

> 1. Edit 20.2.2 [memory.syn], header `<memory>` synopsis, as indicated:

>    >     namespace std {
>    >       […]
>    >       // 20.2.8.2 [allocator.uses.construction], uses-allocator construction
>    >       […]

>    >       template<class T, class Alloc>
>    >         constexpr auto uses_allocator_construction_args(const Alloc& alloc,
>    >                                                         const remove_cv_t<T>& pr) noexcept;
>    >       template<class T, class Alloc, class U, class V>
>    >         constexpr auto uses_allocator_construction_args(const Alloc& alloc,
>    >                                                         const pair<U, V>& pr) noexcept -> see below;

>    >       template<class T, class Alloc>
>    >         constexpr auto uses_allocator_construction_args(const Alloc& alloc,
>    >                                                         remove_cv_t<T>&& pr) noexcept;
>    >       template<class T, class Alloc, class U, class V>
>    >         constexpr auto uses_allocator_construction_args(const Alloc& alloc,
>    >                                                         pair<U, V>&& pr) noexcept -> see below;
>    >       […]
>    >     }

> 2. Edit 20.2.8.2 [allocator.uses.construction] as indicated:

>    >     template<class T, class Alloc>
>    >       constexpr auto uses_allocator_construction_args(const Alloc& alloc,
>    >                                                       const remove_cv_t<T>& pr) noexcept;
>    >     template<class T, class Alloc, class U, class V>
>    >       constexpr auto uses_allocator_construction_args(const Alloc& alloc,
>    >                                                       const pair<U, V>& pr) noexcept -> see below;

>    > > -12- Constraints: `T` is a specialization of `pair`. For the second overload, `is_same_v<pair<U, V>, remove_cv_t<T>>` is `false`.

>    > > -13- Effects: Equivalent to:

>    > > >     return uses_allocator_construction_args<T>(alloc, piecewise_construct,
>    > > >                                                 forward_as_tuple(pr.first),
>    > > >                                                 forward_as_tuple(pr.second));

>    >     template<class T, class Alloc>
>    >       constexpr auto uses_allocator_construction_args(const Alloc& alloc,
>    >                                                       remove_cv_t<T>&& pr) noexcept;
>    >     template<class T, class Alloc, class U, class V>
>    >       constexpr auto uses_allocator_construction_args(const Alloc& alloc,
>    >                                                       pair<U, V>&& pr) noexcept -> see below;

>    > > -14- Constraints: `T` is a specialization of `pair`. For the second overload, `is_same_v<pair<U, V>, remove_cv_t<T>>` is `false`.

>    > > -15- Effects: Equivalent to:

>    > > >     return uses_allocator_construction_args<T>(alloc, piecewise_construct,
>    > > >                                                 forward_as_tuple(std::move(pr).first),
>    > > >                                                 forward_as_tuple(std::move(pr).second));

[2021-03-12; Reflector poll]

Set priority to 3 following reflector poll.

Previous resolution [SUPERSEDED]:

> This wording is relative to N4878.

> 1. Edit 20.2.2 [memory.syn], header `<memory>` synopsis, as indicated:

>    >     namespace std {
>    >       […]
>    >       // 20.2.8.2 [allocator.uses.construction], uses-allocator construction
>    >       […]

>    >       template<class T, class Alloc, class U, class V>
>    >         constexpr auto uses_allocator_construction_args(const Alloc& alloc,
>    >                                                         const pair<U, V>& pr) noexcept -> see below;

>    >       template<class T, class Alloc, class U, class V>
>    >         constexpr auto uses_allocator_construction_args(const Alloc& alloc,
>    >                                                         pair<U, V>&& pr) noexcept -> see below;

>    >       template<class T, class Alloc, class U>
>    >         constexpr auto uses_allocator_construction_args(const Alloc& alloc, U&& u) noexcept;
>    >       […]
>    >     }

> 2. Add the following to 20.2.8.2 [allocator.uses.construction]:

>    >       template<class T, class Alloc, class U>
>    >         constexpr auto uses_allocator_construction_args(const Alloc& alloc, U&& u) noexcept;

>    > > -?- Let `FUN` be the function template:

>    > > >       template<class A, class B>
>    > > >       void FUN(const pair<A, B>&);

>    > > -?- Constraints: `T` is a specialization of `pair`, and the expression `FUN(u)` is not well-formed when considered as an unevaluated operand.

>    > > -?- Effects: Equivalent to:

>    > > >     return make_tuple(pair-constructor{alloc, u});

>    > > where `pair-constructor` is an exposition-only class defined as follows:

>    > > >     struct pair-constructor {
>    > > >       using pair-type = remove_cv_t<T>;            // exposition only

>    > > >       constexpr operator pair-type() const {
>    > > >         return do-construct(std::forward<U>(u));
>    > > >       }

>    > > >       constexpr auto do-construct(const pair-type& p) const {  // exposition only
>    > > >         return make_obj_using_allocator<pair-type>(alloc, p);
>    > > >       }
>    > > >       constexpr auto do-construct(pair-type&& p) const {  // exposition only
>    > > >         return make_obj_using_allocator<pair-type>(alloc, std::move(p));
>    > > >       }

>    > > >       const Alloc& alloc;  // exposition only
>    > > >       U& u;                // exposition only
>    > > >     };

[2021-12-02 Tim updates PR to avoid public exposition-only members]

[2022-01-31; Reflector poll]

Set status to Tentatively Ready after seven votes in favour during reflector poll.

[2022-02-10 Approved at February 2022 virtual plenary. Status changed: Tentatively Ready → WP.]

Proposed resolution:

This wording is relative to N4901.

1. Edit 20.2.2 [memory.syn], header `<memory>` synopsis, as indicated:

   >     namespace std {
   >       […]
   >       // 20.2.8.2 [allocator.uses.construction], uses-allocator construction
   >       […]
   >       template<class T, class Alloc, class U, class V>
   >         constexpr auto uses_allocator_construction_args(const Alloc& alloc,
   >                                                         pair<U, V>& pr) noexcept;

   >       template<class T, class Alloc, class U, class V>
   >         constexpr auto uses_allocator_construction_args(const Alloc& alloc,
   >                                                         const pair<U, V>& pr) noexcept;

   >       template<class T, class Alloc, class U, class V>
   >         constexpr auto uses_allocator_construction_args(const Alloc& alloc,
   >                                                         pair<U, V>&& pr) noexcept;

   >       template<class T, class Alloc, class U, class V>
   >         constexpr auto uses_allocator_construction_args(const Alloc& alloc,
   >                                                         const pair<U, V>&& pr) noexcept;

   >       template<class T, class Alloc, class U>
   >         constexpr auto uses_allocator_construction_args(const Alloc& alloc, U&& u) noexcept;
   >       […]
   >     }

2. Add the following to 20.2.8.2 [allocator.uses.construction]:

   >       template<class T, class Alloc, class U>
   >         constexpr auto uses_allocator_construction_args(const Alloc& alloc, U&& u) noexcept;

   > > -?- Let `FUN` be the function template:

   > > >       template<class A, class B>
   > > >       void FUN(const pair<A, B>&);

   > > -?- Constraints: `T` is a specialization of `pair`, and the expression `FUN(u)` is not well-formed when considered as an unevaluated operand.

   > > -?- Let `pair-constructor` be an exposition-only class defined as follows:

   > > >     class pair-constructor {
   > > >       using pair-type = remove_cv_t<T>;            // exposition only

   > > >       constexpr auto do-construct(const pair-type& p) const {  // exposition only
   > > >         return make_obj_using_allocator<pair-type>(alloc_, p);
   > > >       }
   > > >       constexpr auto do-construct(pair-type&& p) const {  // exposition only
   > > >         return make_obj_using_allocator<pair-type>(alloc_, std::move(p));
   > > >       }

   > > >       const Alloc& alloc_;  // exposition only
   > > >       U& u_;                // exposition only

   > > >     public:
   > > >       constexpr operator pair-type() const {
   > > >         return do-construct(std::forward<U>(u_));
   > > >       }
   > > >     };

   > > -?- Returns: `make_tuple(pc)`, where `pc` is a `pair-constructor` object whose `alloc_` member is initialized with `alloc` and whose `u_` member is initialized with `u`.