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.

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: New Submitter: Jiang An Opened: 2025-03-20 Last modified: 2025-03-22

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 New 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 <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.

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<C>)
      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<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.

    […]