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

3525. uses_allocator_construction_args fails to handle types convertible to pair

Section: 20.2.8.2 [allocator.uses.construction] Status: C++23 Submitter: Tim Song Opened: 2021-02-23 Last modified: 2023-11-22

Priority: 3

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

View all issues with C++23 status.

Discussion:

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

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 pairs 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 pairs.

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.