Created on 2025-08-07.00:00:00 last changed 1 week ago
Proposed resolution:
This wording is relative to N5014.
Modify [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 ([allocator.requirements.general]).
-33- Effects: Equivalent to the preceding constructors except that each element is constructed with uses-allocator construction ([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.
[ 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.
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 [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.History | |||
---|---|---|---|
Date | User | Action | Args |
2025-08-17 15:09:01 | admin | set | messages: + msg14940 |
2025-08-09 17:21:04 | admin | set | messages: + msg14931 |
2025-08-07 00:00:00 | admin | create |