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

4313. Uses-allocator construction of pair in tuple's allocator_arg_t constructors

Section: 22.4.4.2 [tuple.cnstr] Status: New Submitter: Jiang An Opened: 2025-08-07 Last modified: 2025-08-17

Priority: Not Prioritized

View other active issues in [tuple.cnstr].

View all other issues in [tuple.cnstr].

View all issues with New status.

Discussion:

Before P0591R4, only scoped_allocator_adaptor::construct and polymorphic_allocator::construct specially handled pair for the purpose of uses-allocator construction. The primary definition of uses-allocator construction (in e.g., N4659 [allocator.uses.construction]) did not specially handle pair. The allocator_arg_t constructors of tuple, which were specified to construct tuple elements with uses-allocator constructor (per e.g., N4659 [tuple.cnstr] p26), did not specially handle pair either.

P0591R4 redefined uses-allocator construction in terms of make_obj_using_allocator in [allocator.uses.construction] p1 as:

When applied to the construction of an object of type T, it is equivalent to initializing it with the value of the expression make_obj_using_allocator<T>(alloc, args...), described below.

And the new definition does handle pair. As the specification of allocator_arg_t constructors of tuple (now in 22.4.4.2 [tuple.cnstr] p33) still refer to uses-allocator construction as-is, these constructors should construct a pair element in a way equivalent to make_obj_using_allocator now.

The following example shows the behavioral difference.

#include <cstddef>
#include <utility>
#include <tuple>
#include <memory>
#include <vector>
#include <cassert>

template<class T>
class payload_ator {
  int payload{};
   
public:
  using value_type = T;

  payload_ator() = default;

  constexpr explicit payload_ator(int n) noexcept : payload{n} {}

  template<class U>
  constexpr explicit payload_ator(payload_ator<U> a) noexcept : payload{a.payload} {}  

  friend bool operator==(payload_ator, payload_ator) = default;

  template<class U>
  friend constexpr bool operator==(payload_ator x, payload_ator<U> y) noexcept {
    return x.payload == y.payload;
  }  

  constexpr T* allocate(std::size_t n) { return std::allocator<T>{}.allocate(n); }

  constexpr void deallocate(T* p, std::size_t n) { return std::allocator<T>{}.deallocate(p, n); }  

  constexpr int get_payload() const noexcept { return payload; }
};

bool test() {
  constexpr int in_v = 42;
  using my_pair_t = std::pair<int, std::vector<int, payload_ator<int>>>;
  std::tuple<my_pair_t> t(std::allocator_arg, payload_ator<int>{in_v});
  auto out_v = std::get<0>(t).second.get_allocator().get_payload();
  return in_v == out_v;
}

int main() {
  assert(test()); // passes only if allocator_arg_t constructors of tuple specially handle pair
}

However, the behavioral changes of these constructors were not discussed in P0591R4, and existing implementations that claim full implementation of P0591R4 (MSVC STL and libstdc++) did not change these constructors (demo).

Given that implementations did not recognize changes of allocator_arg_t constructors as part of the paper, and special handling of pair would significantly complicate these constructors, perhaps we should explicitly specify that these constructors behave as if special handling for pair were missing.

[2025-08-17; Pablo comments]

I don't agree with it or the PR. It seems like the implementations are simply lagging behind. Since make_obj_using_allocator is in the standard, tuple is easy enough to implement simply by delegating to that function. I regard the failure to handle pair in a general way to be a defect in the C++11 specification and that handling it correctly, though technically a change of behavior, is more likely to fix bugs than to create them. It is exactly this scattershot restatement of uses-allocator construction for pair that I hoped to fix with P0591, even though I missed tuple in my exposition.

Proposed resolution:

This wording is relative to N5014.

  1. Modify 22.4.4.2 [tuple.cnstr] as indicated:

    template<class Alloc>
      constexpr explicit(see below )
        tuple(allocator_arg_t, const Alloc& a);
    […]
    template<class Alloc, class U1, class U2>
      constexpr explicit(see below)
        tuple(allocator_arg_t, const Alloc& a, pair<U1, U2>&);
    template<class Alloc, class U1, class U2>
      constexpr explicit(see below)
        tuple(allocator_arg_t, const Alloc& a, const pair<U1, U2>&);
    template<class Alloc, class U1, class U2>
      constexpr explicit(see below)
        tuple(allocator_arg_t, const Alloc& a, pair<U1, U2>&&);
    template<class Alloc, class U1, class U2>
      constexpr explicit(see below)
        tuple(allocator_arg_t, const Alloc& a, const pair<U1, U2>&&);
    template<class Alloc, tuple-like UTuple>
      constexpr explicit(see below)
      tuple(allocator_arg_t, const Alloc& a, UTuple&&);
    

    -32- Preconditions: Alloc meets the Cpp17Allocator requirements (16.4.4.6.1 [allocator.requirements.general]).

    -33- Effects: Equivalent to the preceding constructors except that each element is constructed with uses-allocator construction (20.2.8.2 [allocator.uses.construction]), except that the construction behaves as if there were only one uses_allocator_construction_args overload and the overload behaved the same as the first actual overload without Constraints.