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.
pair
in tuple
's allocator_arg_t
constructorsSection: 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.
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 expressionmake_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.
#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 ofallocator_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.
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:
-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 oneAlloc
meets the Cpp17Allocator requirements (16.4.4.6.1 [allocator.requirements.general]).uses_allocator_construction_args
overload and the overload behaved the same as the first actual overload without Constraints.