3526. Return types of uses_allocator_construction_args unspecified

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

Priority: 3

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

View all issues with C++23 status.

Discussion:

The synopsis of <memory> in 20.2.2 [memory.syn] declares six overloads of uses_allocator_construction_args with return types "see below":

template<class T, class Alloc, class... Args>
  constexpr auto uses_allocator_construction_args(const Alloc& alloc,
                                                  Args&&... args) noexcept -> see below;
template<class T, class Alloc, class Tuple1, class Tuple2>>
  constexpr auto uses_allocator_construction_args(const Alloc& alloc, piecewise_construct_t,
                                                  Tuple1&& x, Tuple2&& y)
                                                  noexcept -> see below;
template<class T, class Alloc>
  constexpr auto uses_allocator_construction_args(const Alloc& alloc) noexcept -> see below;
template<class T, class Alloc, class U, class V>
  constexpr auto uses_allocator_construction_args(const Alloc& alloc,
                                                  U&& u, V&& v) noexcept -> see below;
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;

The "see belows" also appear in the detailed specification of these overloaded function templates in 20.2.8.2 [allocator.uses.construction] para 4 through 15. Despite that the values these function templates return are specified therein, the return types are not. Presumably LWG wanted to specify deduced return types, but couldn't figure out how to do so, and just gave up?

[2021-02-27; Daniel comments and provides wording]

My interpretation is that the appearance of the trailing-return-type was actually unintended and that these functions where supposed to use the return type placeholder to signal the intention that the actual return type is deduced by the consistent sum of all return statements as they appear in the prototype specifications. Given that at least one implementation has indeed realized this form, I suggest to simply adjust the specification to remove the trailing-return-type. Specification-wise we have already existing practice for this approach (See e.g. to_address).

[2021-03-12; Reflector poll]

Set priority to 3 following reflector poll.

[2021-05-26; Reflector poll]

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

[2021-06-07 Approved at June 2021 virtual plenary. Status changed: Voting → WP.]

Proposed resolution:

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... Args>
        constexpr auto uses_allocator_construction_args(const Alloc& alloc,
                                                        Args&&... args) noexcept -> see below;
      template<class T, class Alloc, class Tuple1, class Tuple2>
        constexpr auto uses_allocator_construction_args(const Alloc& alloc, piecewise_construct_t,
                                                        Tuple1&& x, Tuple2&& y)
                                                        noexcept -> see below;
      template<class T, class Alloc>
        constexpr auto uses_allocator_construction_args(const Alloc& alloc) noexcept -> see below;
      template<class T, class Alloc, class U, class V>
        constexpr auto uses_allocator_construction_args(const Alloc& alloc,
                                                        U&& u, V&& v) noexcept -> see below;
      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;   
      […]
    }
    
  2. Edit 20.2.8.2 [allocator.uses.construction] as indicated:

    template<class T, class Alloc, class... Args>
      constexpr auto uses_allocator_construction_args(const Alloc& alloc,
                                                      Args&&... args) noexcept -> see below;
    

    […]

    -5- Returns: A tuple value determined as follows:

    1. (5.1) — if […], return forward_as_tuple(std::forward<Args>(args)...).

    2. (5.2) — Otherwise, if […], return

      tuple<allocator_arg_t, const Alloc&, Args&&...>(
        allocator_arg, alloc, std::forward<Args>(args)...)
      
    3. (5.3) — Otherwise, if […], return forward_as_tuple(std::forward<Args>(args)..., alloc).

    4. (5.4) — Otherwise, the program is ill-formed.

    template<class T, class Alloc, class Tuple1, class Tuple2>
      constexpr auto uses_allocator_construction_args(const Alloc& alloc, piecewise_construct_t,
                                                      Tuple1&& x, Tuple2&& y)
                                                      noexcept -> see below;
    

    […]

    -7- Effects: For T specified as pair<T1, T2>, equivalent to:

    return make_tuple(
      piecewise_construct,
      apply([&alloc](auto&&... args1) {
              return uses_allocator_construction_args<T1>(
                alloc, std::forward<decltype(args1)>(args1)...);
            }, std::forward<Tuple1>(x)),
      apply([&alloc](auto&&... args2) {
              return uses_allocator_construction_args<T2>(
                alloc, std::forward<decltype(args2)>(args2)...);
            }, std::forward<Tuple2>(y)));
    
    template<class T, class Alloc>
      constexpr auto uses_allocator_construction_args(const Alloc& alloc) noexcept -> see below;
    

    […]

    -9- Effects: Equivalent to:

    return uses_allocator_construction_args<T>(alloc, piecewise_construct,
                                               tuple<>{}, tuple<>{});
    
    template<class T, class Alloc, class U, class V>
      constexpr auto uses_allocator_construction_args(const Alloc& alloc,
                                                      U&& u, V&& v) noexcept -> see below;
    

    […]

    -11- Effects: Equivalent to:

    return uses_allocator_construction_args<T>(alloc, piecewise_construct,
                                               forward_as_tuple(std::forward<U>(u)),
                                               forward_as_tuple(std::forward<V>(v));
    
    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;
    

    […]

    -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, class U, class V>
      constexpr auto uses_allocator_construction_args(const Alloc& alloc,
                                                      pair<U, V>&& pr) noexcept -> see below;
    

    […]

    -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));