Created on 2025-03-20.00:00:00 last changed 3 days ago
Proposed resolution:
This wording is relative to N5008.
Modify [range.utility.conv.to] as indicated:
template<class C, input_range R, class... Args> requires (!view<C>) constexpr C to(R&& r, Args&&... args);-1- Mandates: `C` is a cv-unqualified non-union class type.
[…]
Modify [range.utility.conv.adaptors] as indicated:
template<class C, class... Args> requires (!view<C>) constexpr auto to(Args&&... args); template<template<class...> class C, class... Args> constexpr auto to(Args&&... args);-1- Mandates: For the first overload, `C` is a cv-unqualified non-union class type.
[…]
[ 2025-12-10 Status changed: Tentatively NAD → NAD. ]
[ 2025-10-20; Reflector poll; Status changed: New → Tentatively NAD. ]
"Those implementations have bugs and should be fixed."
There's no intrinsic reason why unions (with suitable constructors) should be rejected here."
LWG 3847 made `std::ranges::to` require the return type (or the target type for the overload returning range adaptor closure object) to be a cv-unqualified class type. Although the term "class type" in core language specification also covers union types, implementations (libstdc++ and MSVC STL) tend to implement this part of the Mandates only with `std::is_class_v`, which rejects union types.
E.g. the following program is rejected by libstdc++ and MSVC STL (https://godbolt.org/z/MnsY4Tzen):
#include <memory>
#include <ranges>
#include <type_traits>
#include <utility>
#include <vector>
template<class T, class A = std::allocator<T>>
union weird_vector {
std::vector<T, A> vec_;
constexpr weird_vector() : vec_() {}
constexpr weird_vector(const weird_vector& other) : vec_(other.vec_) {}
constexpr weird_vector(weird_vector&& other) noexcept : vec_(std::move(other.vec_)) {}
template<class U>
requires (!std::same_as<std::remove_cvref_t<U>, weird_vector>) &&
(!std::same_as<std::remove_cvref_t<U>, std::vector<T, A>>) &&
requires(U&& u) { std::vector<T, A>(std::forward<U>(u)); }
constexpr explicit weird_vector(U&& u) : vec_(std::forward<U>(u)) {}
template<class T1, class T2, class... Ts>
requires requires(T1&& t1, T2&& t2, Ts&&... ts) {
std::vector<T, A>(std::forward<T1>(t1), std::forward<T2>(t2), std::forward<Ts>(ts)...);
}
constexpr weird_vector(T1&& t1, T2&& t2, Ts&&... ts)
: vec_(std::forward<T1>(t1), std::forward<T2>(t2), std::forward<Ts>(ts)...) {}
constexpr weird_vector& operator=(const weird_vector& other) {
vec_ = other.vec_;
return *this;
}
constexpr weird_vector& operator=(weird_vector&& other)
noexcept(std::is_nothrow_move_assignable_v<std::vector<T, A>>) {
vec_ = std::move(other.vec_);
return *this;
}
constexpr ~weird_vector() {
vec_.~vector();
}
};
int main() {
int arr[]{42, 1729};
auto v [[maybe_unused]] = std::ranges::to<weird_vector<int>>(arr);
}
Although libc++ currently accepts this example, the acceptance seems to be a bug, because libc++ hasn't implemented the "class" part in the Mandates at all (llvm/llvm-project#132133).
It's unclear whether union types were intended to be accepted. Perhaps we should follow implementations' choices and reject them.| History | |||
|---|---|---|---|
| Date | User | Action | Args |
| 2025-12-10 23:00:58 | admin | set | messages: + msg15804 |
| 2025-10-20 16:17:38 | admin | set | messages: + msg15283 |
| 2025-10-20 16:17:38 | admin | set | status: new -> nad |
| 2025-03-22 13:20:46 | admin | set | messages: + msg14694 |
| 2025-03-20 00:00:00 | admin | create | |