Issue 4229: std::ranges::to with union return type (original) (raw)


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

4229. std::ranges::to with union return type

Section: 25.5.7.2 [range.utility.conv.to], 25.5.7.3 [range.utility.conv.adaptors] Status: NAD Submitter: Jiang An Opened: 2025-03-20 Last modified: 2025-12-10

Priority: Not Prioritized

View other active issues in [range.utility.conv.to].

View all other issues in [range.utility.conv.to].

View all issues with NAD status.

Discussion:

LWG 3847(i) 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 #include #include #include #include

template<class T, class A = std::allocator> 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 requires (!std::same_as<std::remove_cvref_t, weird_vector>) && (!std::same_as<std::remove_cvref_t, std::vector<T, A>>) && requires(U&& u) { std::vector<T, A>(std::forward(u)); } constexpr explicit weird_vector(U&& u) : vec_(std::forward(u)) {}

template<class T1, class T2, class... Ts> requires requires(T1&& t1, T2&& t2, Ts&&... ts) { std::vector<T, A>(std::forward(t1), std::forward(t2), std::forward(ts)...); } constexpr weird_vector(T1&& t1, T2&& t2, Ts&&... ts) : vec_(std::forward(t1), std::forward(t2), std::forward(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>(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.

[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."

[2025-12-10 Status changed: Tentatively NAD → NAD.]

Proposed resolution:

This wording is relative to N5008.

  1. Modify 25.5.7.2 [range.utility.conv.to] as indicated:

    template<class C, input_range R, class... Args> requires (!view)
    constexpr C to(R&& r, Args&&... args);

    -1- Mandates: C is a cv-unqualified non-union class type.

    […]

  2. Modify 25.5.7.3 [range.utility.conv.adaptors] as indicated:

    template<class C, class... Args> requires (!view)
    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.

    […]