From 993270ab446bb0f2157a036d94d8efde656623ea Mon Sep 17 00:00:00 2001 From: "A. Jiang" Date: Tue, 21 Oct 2025 10:11:57 +0800 Subject: [PATCH 1/2] Make `ranges::to` support creating unions Union types are also class type in the C++ core language, and [range.utility.conv.adaptors] doesn't exclude them. LWG-4229 attempted to clarify that unions are unsupported, but it's recently set to Tentatively NAD. As a result, we should support creating unions. --- stl/inc/__msvc_ranges_to.hpp | 8 ++- .../std/tests/P1206R7_ranges_to_misc/test.cpp | 58 +++++++++++++++++++ 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/stl/inc/__msvc_ranges_to.hpp b/stl/inc/__msvc_ranges_to.hpp index de0fc9baead..794a28ee57f 100644 --- a/stl/inc/__msvc_ranges_to.hpp +++ b/stl/inc/__msvc_ranges_to.hpp @@ -1108,7 +1108,8 @@ namespace ranges { _NODISCARD constexpr _Container to(_Rng&& _Range, _Types&&... _Args) { static_assert(!is_const_v<_Container>, "C must not be const. ([range.utility.conv.to])"); static_assert(!is_volatile_v<_Container>, "C must not be volatile. ([range.utility.conv.to])"); - static_assert(is_class_v<_Container>, "C must be a class type. ([range.utility.conv.to])"); + static_assert(is_class_v<_Container> || is_union_v<_Container>, + "C must be a class type (including union). ([range.utility.conv.to])"); if constexpr (_Ref_converts<_Rng, _Container>) { if constexpr (constructible_from<_Container, _Rng, _Types...>) { return _Container(_STD forward<_Rng>(_Range), _STD forward<_Types>(_Args)...); @@ -1161,7 +1162,7 @@ namespace ranges { struct _To_class_fn { _STL_INTERNAL_STATIC_ASSERT(!is_const_v<_Container>); _STL_INTERNAL_STATIC_ASSERT(!is_volatile_v<_Container>); - _STL_INTERNAL_STATIC_ASSERT(is_class_v<_Container>); + _STL_INTERNAL_STATIC_ASSERT(is_class_v<_Container> || is_union_v<_Container>); _STL_INTERNAL_STATIC_ASSERT(!view<_Container>); template @@ -1178,7 +1179,8 @@ namespace ranges { _NODISCARD constexpr auto to(_Types&&... _Args) { static_assert(!is_const_v<_Container>, "C must not be const. ([range.utility.conv.adaptors])"); static_assert(!is_volatile_v<_Container>, "C must not be volatile. ([range.utility.conv.adaptors])"); - static_assert(is_class_v<_Container>, "C must be a class type. ([range.utility.conv.adaptors])"); + static_assert(is_class_v<_Container> || is_union_v<_Container>, + "C must be a class type (including union). ([range.utility.conv.adaptors])"); return _Range_closure<_To_class_fn<_Container>, decay_t<_Types>...>{_STD forward<_Types>(_Args)...}; } diff --git a/tests/std/tests/P1206R7_ranges_to_misc/test.cpp b/tests/std/tests/P1206R7_ranges_to_misc/test.cpp index f168fd834be..a378c316bbf 100644 --- a/tests/std/tests/P1206R7_ranges_to_misc/test.cpp +++ b/tests/std/tests/P1206R7_ranges_to_misc/test.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -160,6 +161,60 @@ constexpr bool test_nested_range() { return true; } +template > +union union_vector { + std::vector vec_; + + constexpr union_vector() : vec_() {} + constexpr union_vector(const union_vector& other) : vec_(other.vec_) {} + constexpr union_vector(union_vector&& other) noexcept : vec_(std::move(other.vec_)) {} + + template + requires (!std::same_as, union_vector>) + && (!std::same_as, std::vector>) + && requires(U&& u) { std::vector(std::forward(u)); } + constexpr explicit union_vector(U&& u) : vec_(std::forward(u)) {} + + template + requires requires(T1&& t1, T2&& t2, Ts&&... ts) { + std::vector(std::forward(t1), std::forward(t2), std::forward(ts)...); + } + constexpr union_vector(T1&& t1, T2&& t2, Ts&&... ts) + : vec_(std::forward(t1), std::forward(t2), std::forward(ts)...) {} + + constexpr union_vector& operator=(const union_vector& other) { + vec_ = other.vec_; + return *this; + } + constexpr union_vector& operator=(union_vector&& other) + noexcept(std::is_nothrow_move_assignable_v>) { + vec_ = std::move(other.vec_); + return *this; + } + + constexpr ~union_vector() noexcept { + vec_.~vector(); + } +}; + +template >> +union_vector(std::from_range_t, R&&, A = A()) -> union_vector, A>; + +constexpr bool test_to_union() { + constexpr int src[]{42, 1729}; + + assert(ranges::equal(ranges::to>(src).vec_, src)); + assert(ranges::equal((src | ranges::to>()).vec_, src)); + + static_assert(std::same_as(src)), union_vector>); + assert(ranges::equal(ranges::to(src).vec_, src)); + + static_assert(std::same_as()), union_vector>); + assert(ranges::equal((src | ranges::to()).vec_, src)); + + return true; +} + struct ContainerLike { template constexpr ContainerLike(Iter first, Iter last) : dist(static_cast(ranges::distance(first, last))) {} @@ -370,6 +425,9 @@ int main() { test_nested_range(); static_assert(test_nested_range()); + test_to_union(); + static_assert(test_to_union()); + test_lwg3733(); static_assert(test_lwg3733()); From 89dc2a35668ca0f07aa431f01274585b0c6ba9be Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Wed, 22 Oct 2025 01:48:56 -0700 Subject: [PATCH 2/2] Cite the Standard more precisely, avoid adding "(including union)". --- stl/inc/__msvc_ranges_to.hpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/stl/inc/__msvc_ranges_to.hpp b/stl/inc/__msvc_ranges_to.hpp index 794a28ee57f..a36bbd6df36 100644 --- a/stl/inc/__msvc_ranges_to.hpp +++ b/stl/inc/__msvc_ranges_to.hpp @@ -1106,10 +1106,10 @@ namespace ranges { _EXPORT_STD template requires (!view<_Container>) _NODISCARD constexpr _Container to(_Rng&& _Range, _Types&&... _Args) { - static_assert(!is_const_v<_Container>, "C must not be const. ([range.utility.conv.to])"); - static_assert(!is_volatile_v<_Container>, "C must not be volatile. ([range.utility.conv.to])"); + static_assert(!is_const_v<_Container>, "C must not be const. (N5014 [range.utility.conv.to]/1)"); + static_assert(!is_volatile_v<_Container>, "C must not be volatile. (N5014 [range.utility.conv.to]/1)"); static_assert(is_class_v<_Container> || is_union_v<_Container>, - "C must be a class type (including union). ([range.utility.conv.to])"); + "C must be a class type. (N5014 [range.utility.conv.to]/1)"); if constexpr (_Ref_converts<_Rng, _Container>) { if constexpr (constructible_from<_Container, _Rng, _Types...>) { return _Container(_STD forward<_Rng>(_Range), _STD forward<_Types>(_Args)...); @@ -1143,7 +1143,7 @@ namespace ranges { } else { static_assert(false, "ranges::to requires the result to be constructible from the source range, either " "by using a suitable constructor, or by inserting each element of the range into " - "the default-constructed object. (N4981 [range.utility.conv.to]/2.1.5)"); + "the default-constructed object. (N5014 [range.utility.conv.to]/2.1.5)"); } } else if constexpr (input_range>) { const auto _Xform = [](auto&& _Elem) _STATIC_LAMBDA { @@ -1154,7 +1154,7 @@ namespace ranges { static_assert(false, "ranges::to requires the elements of the source range to be either implicitly convertible to the " "elements of the destination container, or be ranges themselves for ranges::to to be applied " - "recursively. (N4981 [range.utility.conv.to]/2.3)"); + "recursively. (N5014 [range.utility.conv.to]/2.3)"); } } @@ -1177,10 +1177,10 @@ namespace ranges { _EXPORT_STD template requires (!view<_Container>) _NODISCARD constexpr auto to(_Types&&... _Args) { - static_assert(!is_const_v<_Container>, "C must not be const. ([range.utility.conv.adaptors])"); - static_assert(!is_volatile_v<_Container>, "C must not be volatile. ([range.utility.conv.adaptors])"); + static_assert(!is_const_v<_Container>, "C must not be const. (N5014 [range.utility.conv.adaptors]/1)"); + static_assert(!is_volatile_v<_Container>, "C must not be volatile. (N5014 [range.utility.conv.adaptors]/1)"); static_assert(is_class_v<_Container> || is_union_v<_Container>, - "C must be a class type (including union). ([range.utility.conv.adaptors])"); + "C must be a class type. (N5014 [range.utility.conv.adaptors]/1)"); return _Range_closure<_To_class_fn<_Container>, decay_t<_Types>...>{_STD forward<_Types>(_Args)...}; }