[libc++][ranges] Implement `ranges::to`.
authorvarconst <varconsteq@gmail.com>
Sat, 15 Jul 2023 03:54:38 +0000 (20:54 -0700)
committerKonstantin Varlamov <varconsteq@gmail.com>
Fri, 21 Jul 2023 05:48:18 +0000 (22:48 -0700)
Differential Revision: https://reviews.llvm.org/D142335

32 files changed:
libcxx/docs/FeatureTestMacroTable.rst
libcxx/docs/ReleaseNotes/17.rst
libcxx/docs/Status/Cxx23Issues.csv
libcxx/docs/Status/Cxx23Papers.csv
libcxx/docs/Status/RangesMajorFeatures.csv
libcxx/include/CMakeLists.txt
libcxx/include/__ranges/to.h [new file with mode: 0644]
libcxx/include/module.modulemap.in
libcxx/include/ranges
libcxx/include/version
libcxx/modules/std/ranges.cppm
libcxx/test/libcxx/ranges/range.utility/range.utility.conv/to.internal_constraints.verify.cpp [new file with mode: 0644]
libcxx/test/libcxx/ranges/range.utility/range.utility.conv/to.nodiscard.verify.cpp [new file with mode: 0644]
libcxx/test/libcxx/ranges/range.utility/range.utility.conv/to.static_assert.verify.cpp [new file with mode: 0644]
libcxx/test/std/language.support/support.limits/support.limits.general/deque.version.compile.pass.cpp
libcxx/test/std/language.support/support.limits/support.limits.general/forward_list.version.compile.pass.cpp
libcxx/test/std/language.support/support.limits/support.limits.general/list.version.compile.pass.cpp
libcxx/test/std/language.support/support.limits/support.limits.general/map.version.compile.pass.cpp
libcxx/test/std/language.support/support.limits/support.limits.general/queue.version.compile.pass.cpp
libcxx/test/std/language.support/support.limits/support.limits.general/ranges.version.compile.pass.cpp
libcxx/test/std/language.support/support.limits/support.limits.general/set.version.compile.pass.cpp
libcxx/test/std/language.support/support.limits/support.limits.general/stack.version.compile.pass.cpp
libcxx/test/std/language.support/support.limits/support.limits.general/string.version.compile.pass.cpp
libcxx/test/std/language.support/support.limits/support.limits.general/unordered_map.version.compile.pass.cpp
libcxx/test/std/language.support/support.limits/support.limits.general/unordered_set.version.compile.pass.cpp
libcxx/test/std/language.support/support.limits/support.limits.general/vector.version.compile.pass.cpp
libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
libcxx/test/std/ranges/range.utility/range.utility.conv/container.h [new file with mode: 0644]
libcxx/test/std/ranges/range.utility/range.utility.conv/to.pass.cpp [new file with mode: 0644]
libcxx/test/std/ranges/range.utility/range.utility.conv/to_deduction.pass.cpp [new file with mode: 0644]
libcxx/test/std/ranges/range.utility/range.utility.conv/to_std_containers.pass.cpp [new file with mode: 0644]
libcxx/utils/generate_feature_test_macro_components.py

index 79c14e0..1fdc27b 100644 (file)
@@ -358,7 +358,7 @@ Status
     --------------------------------------------------- -----------------
     ``__cpp_lib_ranges_starts_ends_with``               *unimplemented*
     --------------------------------------------------- -----------------
-    ``__cpp_lib_ranges_to_container``                   *unimplemented*
+    ``__cpp_lib_ranges_to_container``                   ``202202L``
     --------------------------------------------------- -----------------
     ``__cpp_lib_ranges_zip``                            *unimplemented*
     --------------------------------------------------- -----------------
index a21e428..2bb18c7 100644 (file)
@@ -55,6 +55,7 @@ Work has started on the C++17 Parallel STL. This feature is experimental, see
 
 Implemented Papers
 ------------------
+- P1206R7 - ``ranges::to``: A function to convert any range to a container
 - P2520R0 - ``move_iterator<T*>`` should be a random access iterator
 - P1328R1 - ``constexpr type_info::operator==()``
 - P1413R3 - Formatting ``thread::id`` (the ``stacktrace`` is not done yet)
index 0324c0f..0cc0667 100644 (file)
 "`3820 <https://wg21.link/LWG3820>`__","``cartesian_product_view::iterator::prev`` is not quite right","February 2023","","","|ranges|"
 "`3825 <https://wg21.link/LWG3825>`__","Missing compile-time argument ``id`` check in ``basic_format_parse_context::next_arg_id``","February 2023","|Complete|","17.0","|format|"
 "`3204 <https://wg21.link/LWG3204>`__","``sub_match::swap`` only swaps the base class","February 2023","|Complete|","17.0",""
-"`3733 <https://wg21.link/LWG3733>`__","``ranges::to`` misuses ``cpp17-input-iterator``","February 2023","","","|ranges|"
+"`3733 <https://wg21.link/LWG3733>`__","``ranges::to`` misuses ``cpp17-input-iterator``","February 2023","|Complete|","17.0","|ranges|"
 "`3742 <https://wg21.link/LWG3742>`__","``deque::prepend_range`` needs to permute","February 2023","","","|ranges|"
 "`3790 <https://wg21.link/LWG3790>`__","`P1467 <https://wg21.link/P1467>`__ accidentally changed ``nexttoward``'s signature","February 2023","","",""
 "`3819 <https://wg21.link/LWG3819>`__","``reference_meows_from_temporary`` should not use ``is_meowible``","February 2023","","",""
 "`3833 <https://wg21.link/LWG3833>`__","Remove specialization ``template<size_t N> struct formatter<const charT[N], charT>``","February 2023","|Complete|","17.0","|format|"
 "`3836 <https://wg21.link/LWG3836>`__","``std::expected<bool, E1>`` conversion constructor ``expected(const expected<U, G>&)`` should take precedence over ``expected(U&&)`` with operator ``bool``","February 2023","","",""
 "`3843 <https://wg21.link/LWG3843>`__","``std::expected<T,E>::value() &`` assumes ``E`` is copy constructible","February 2023","|Complete|","17.0",""
-"`3847 <https://wg21.link/LWG3847>`__","``ranges::to`` can still return views","February 2023","","","|ranges|"
+"`3847 <https://wg21.link/LWG3847>`__","``ranges::to`` can still return views","February 2023","|Complete|","17.0","|ranges|"
 "`3862 <https://wg21.link/LWG3862>`__","``basic_const_iterator``'s ``common_type`` specialization is underconstrained","February 2023","","",""
 "`3865 <https://wg21.link/LWG3865>`__","Sorting a range of ``pairs``","February 2023","|Complete|","17.0","|ranges|"
 "`3869 <https://wg21.link/LWG3869>`__","Deprecate ``std::errc`` constants related to UNIX STREAMS","February 2023","","",""
index b8ea096..9d10fc0 100644 (file)
@@ -41,7 +41,7 @@
 "`P0323R12 <https://wg21.link/P0323R12>`__","LWG","``std::expected``","February 2022","|Complete|","16.0"
 "`P0533R9 <https://wg21.link/P0533R9>`__","LWG","``constexpr`` for ``<cmath>`` and ``<cstdlib>``","February 2022","|In progress| [#note-P0533R9]_",""
 "`P0627R6 <https://wg21.link/P0627R6>`__","LWG","Function to mark unreachable code","February 2022","|Complete|","15.0"
-"`P1206R7 <https://wg21.link/P1206R7>`__","LWG","``ranges::to``: A function to convert any range to a container","February 2022","|In Progress|","","|ranges|"
+"`P1206R7 <https://wg21.link/P1206R7>`__","LWG","``ranges::to``: A function to convert any range to a container","February 2022","|Complete|","17.0","|ranges|"
 "`P1413R3 <https://wg21.link/P1413R3>`__","LWG","Deprecate ``std::aligned_storage`` and ``std::aligned_union``","February 2022","|Complete| [#note-P1413R3]_",""
 "`P2255R2 <https://wg21.link/P2255R2>`__","LWG","A type trait to detect reference binding to temporary","February 2022","",""
 "`P2273R3 <https://wg21.link/P2273R3>`__","LWG","Making ``std::unique_ptr`` constexpr","February 2022","|Complete|","16.0"
index d569f14..259a021 100644 (file)
@@ -1,4 +1,4 @@
 Standard,Name,Assignee,CL,Status
-C++23,`ranges::to <https://wg21.link/P1206R7>`_,Unassigned,No patch yet,Not started
+C++23,`ranges::to <https://wg21.link/P1206R7>`_,Konstantin Varlamov,`D142335 <https://reviews.llvm.org/D142335>`_,Complete
 C++23,`Pipe support for user-defined range adaptors <https://wg21.link/P2387R3>`_,Unassigned,No patch yet,Not started
 C++23,`Formatting Ranges <https://wg21.link/P2286R8>`_,Mark de Wever,Various,Complete
index 2bba97f..8d03e1b 100644 (file)
@@ -646,6 +646,7 @@ set(files
   __ranges/subrange.h
   __ranges/take_view.h
   __ranges/take_while_view.h
+  __ranges/to.h
   __ranges/transform_view.h
   __ranges/view_interface.h
   __ranges/views.h
diff --git a/libcxx/include/__ranges/to.h b/libcxx/include/__ranges/to.h
new file mode 100644 (file)
index 0000000..95c300b
--- /dev/null
@@ -0,0 +1,247 @@
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBCPP___RANGES_TO_H
+#define _LIBCPP___RANGES_TO_H
+
+#include <__algorithm/ranges_copy.h>
+#include <__concepts/constructible.h>
+#include <__concepts/convertible_to.h>
+#include <__concepts/derived_from.h>
+#include <__concepts/same_as.h>
+#include <__config>
+#include <__functional/bind_back.h>
+#include <__iterator/back_insert_iterator.h>
+#include <__iterator/insert_iterator.h>
+#include <__iterator/iterator_traits.h>
+#include <__ranges/access.h>
+#include <__ranges/concepts.h>
+#include <__ranges/from_range.h>
+#include <__ranges/range_adaptor.h>
+#include <__ranges/size.h>
+#include <__ranges/transform_view.h>
+#include <__type_traits/add_pointer.h>
+#include <__type_traits/is_const.h>
+#include <__type_traits/is_volatile.h>
+#include <__type_traits/type_identity.h>
+#include <__utility/declval.h>
+#include <__utility/forward.h>
+#include <cstddef>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#  pragma GCC system_header
+#endif
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+#if _LIBCPP_STD_VER >= 23
+
+namespace ranges {
+
+// TODO(clang-15): in the Standard, it's a `constexpr bool` variable, not a concept, but constexpr variables don't
+// short-circuit properly on Clang 15 (fixed in later versions), so use a concept as a workaround.
+template <class _Container>
+concept __reservable_container = sized_range<_Container> && requires(_Container& __c, range_size_t<_Container> __n) {
+  __c.reserve(__n);
+  { __c.capacity() } -> same_as<decltype(__n)>;
+  { __c.max_size() } -> same_as<decltype(__n)>;
+};
+
+template <class _Container, class _Ref>
+constexpr bool __container_insertable = requires(_Container& __c, _Ref&& __ref) {
+  requires(
+      requires { __c.push_back(std::forward<_Ref>(__ref)); } ||
+      requires { __c.insert(__c.end(), std::forward<_Ref>(__ref)); });
+};
+
+template <class _Ref, class _Container>
+_LIBCPP_HIDE_FROM_ABI constexpr auto __container_inserter(_Container& __c) {
+  if constexpr (requires { __c.push_back(std::declval<_Ref>()); }) {
+    return std::back_inserter(__c);
+  } else {
+    return std::inserter(__c, __c.end());
+  }
+}
+
+// Note: making this a concept allows short-circuiting the second condition.
+template <class _Container, class _Range>
+concept __try_non_recursive_conversion =
+    !input_range<_Container> || convertible_to<range_reference_t<_Range>, range_value_t<_Container>>;
+
+template <class _Container, class _Range, class... _Args>
+concept __constructible_from_iter_pair =
+    common_range<_Range> && requires { typename iterator_traits<iterator_t<_Range>>::iterator_category; } &&
+    derived_from<typename iterator_traits<iterator_t<_Range>>::iterator_category, input_iterator_tag> &&
+    constructible_from<_Container, iterator_t<_Range>, sentinel_t<_Range>, _Args...>;
+
+template <class>
+concept __always_false = false;
+
+// `ranges::to` base template -- the `_Container` type is a simple type template parameter.
+template <class _Container, input_range _Range, class... _Args>
+  requires(!view<_Container>)
+_LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr _Container to(_Range&& __range, _Args&&... __args) {
+  // Mandates: C is a cv-unqualified class type.
+  static_assert(!is_const_v<_Container>, "The target container cannot be const-qualified, please remove the const");
+  static_assert(
+      !is_volatile_v<_Container>, "The target container cannot be volatile-qualified, please remove the volatile");
+
+  // First see if the non-recursive case applies -- the conversion target is either:
+  // - a range with a convertible value type;
+  // - a non-range type which might support being created from the input argument(s) (e.g. an `optional`).
+  if constexpr (__try_non_recursive_conversion<_Container, _Range>) {
+    // Case 1 -- construct directly from the given range.
+    if constexpr (constructible_from<_Container, _Range, _Args...>) {
+      return _Container(std::forward<_Range>(__range), std::forward<_Args>(__args)...);
+    }
+
+    // Case 2 -- construct using the `from_range_t` tagged constructor.
+    else if constexpr (constructible_from<_Container, from_range_t, _Range, _Args...>) {
+      return _Container(from_range, std::forward<_Range>(__range), std::forward<_Args>(__args)...);
+    }
+
+    // Case 3 -- construct from a begin-end iterator pair.
+    else if constexpr (__constructible_from_iter_pair<_Container, _Range, _Args...>) {
+      return _Container(ranges::begin(__range), ranges::end(__range), std::forward<_Args>(__args)...);
+    }
+
+    // Case 4 -- default-construct (or construct from the extra arguments) and insert, reserving the size if possible.
+    else if constexpr (constructible_from<_Container, _Args...> &&
+                       __container_insertable<_Container, range_reference_t<_Range>>) {
+      _Container __result(std::forward<_Args>(__args)...);
+      if constexpr (sized_range<_Range> && __reservable_container<_Container>) {
+        __result.reserve(static_cast<range_size_t<_Container>>(ranges::size(__range)));
+      }
+
+      ranges::copy(__range, ranges::__container_inserter<range_reference_t<_Range>>(__result));
+
+      return __result;
+
+    } else {
+      static_assert(__always_false<_Container>, "ranges::to: unable to convert to the given container type.");
+    }
+
+    // Try the recursive case.
+  } else if constexpr (input_range<range_reference_t<_Range>>) {
+    return ranges::to<_Container>(
+        __range | views::transform([](auto&& __elem) {
+          return ranges::to<range_value_t<_Container>>(std::forward<decltype(__elem)>(__elem));
+        }),
+        std::forward<_Args>(__args)...);
+
+  } else {
+    static_assert(__always_false<_Container>, "ranges::to: unable to convert to the given container type.");
+  }
+}
+
+template <class _Range>
+struct __minimal_input_iterator {
+  using iterator_category = input_iterator_tag;
+  using value_type        = range_value_t<_Range>;
+  using difference_type   = ptrdiff_t;
+  using pointer           = add_pointer_t<range_reference_t<_Range>>;
+  using reference         = range_reference_t<_Range>;
+
+  reference operator*() const;
+  pointer operator->() const;
+  __minimal_input_iterator& operator++();
+  __minimal_input_iterator operator++(int);
+  bool operator==(const __minimal_input_iterator&) const;
+};
+
+// Deduces the full type of the container from the given template template parameter.
+template <template <class...> class _Container, input_range _Range, class... _Args>
+struct _Deducer {
+  _LIBCPP_HIDE_FROM_ABI static constexpr auto __deduce_func() {
+    using _InputIter = __minimal_input_iterator<_Range>;
+
+    // Case 1 -- can construct directly from the given range.
+    if constexpr (requires { _Container(std::declval<_Range>(), std::declval<_Args>()...); }) {
+      using _Result = decltype( //
+          _Container(std::declval<_Range>(), std::declval<_Args>()...));
+      return type_identity<_Result>{};
+
+      // Case 2 -- can construct from the given range using the `from_range_t` tagged constructor.
+    } else if constexpr ( //
+        requires { _Container(from_range, std::declval<_Range>(), std::declval<_Args>()...); }) {
+      using _Result = //
+          decltype(_Container(from_range, std::declval<_Range>(), std::declval<_Args>()...));
+      return type_identity<_Result>{};
+
+      // Case 3 -- can construct from a begin-end iterator pair.
+    } else if constexpr ( //
+        requires { _Container(std::declval<_InputIter>(), std::declval<_InputIter>(), std::declval<_Args>()...); }) {
+      using _Result =
+          decltype(_Container(std::declval<_InputIter>(), std::declval<_InputIter>(), std::declval<_Args>()...));
+      return type_identity<_Result>{};
+
+    } else {
+      static_assert(__always_false<_Range>,
+                    "ranges::to: unable to deduce the container type from the template template argument.");
+    }
+  }
+
+  using type = typename decltype(__deduce_func())::type;
+};
+
+// `ranges::to` specialization -- `_Container` is a template template parameter requiring deduction to figure out the
+// container element type.
+template <template <class...> class _Container, input_range _Range, class... _Args>
+_LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr auto to(_Range&& __range, _Args&&... __args) {
+  using _DeduceExpr = typename _Deducer<_Container, _Range, _Args...>::type;
+  return ranges::to<_DeduceExpr>(std::forward<_Range>(__range), std::forward<_Args>(__args)...);
+}
+
+// Range adaptor closure object 1 -- wrapping the `ranges::to` version where `_Container` is a simple type template
+// parameter.
+template <class _Container, class... _Args>
+  requires(!view<_Container>)
+_LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr auto to(_Args&&... __args) {
+  // Mandates: C is a cv-unqualified class type.
+  static_assert(!is_const_v<_Container>, "The target container cannot be const-qualified, please remove the const");
+  static_assert(
+      !is_volatile_v<_Container>, "The target container cannot be volatile-qualified, please remove the volatile");
+
+  auto __to_func = []<input_range _Range, class... _Tail>(_Range && __range, _Tail && ... __tail)
+    requires requires { //
+      /**/ ranges::to<_Container>(std::forward<_Range>(__range), std::forward<_Tail>(__tail)...);
+    }
+  {
+    return ranges::to<_Container>(std::forward<_Range>(__range), std::forward<_Tail>(__tail)...);
+  };
+
+  return __range_adaptor_closure_t(std::__bind_back(__to_func, std::forward<_Args>(__args)...));
+}
+
+// Range adaptor closure object 2 -- wrapping the `ranges::to` version where `_Container` is a template template
+// parameter.
+template <template <class...> class _Container, class... _Args>
+_LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr auto to(_Args&&... __args) {
+  // clang-format off
+  auto __to_func = []<input_range _Range, class... _Tail,
+                      class _DeducedExpr = typename _Deducer<_Container, _Range, _Tail...>::type>
+    (_Range&& __range, _Tail&& ... __tail)
+      requires requires { //
+      /**/ ranges::to<_DeducedExpr>(std::forward<_Range>(__range), std::forward<_Tail>(__tail)...);
+    }
+  {
+    return ranges::to<_DeducedExpr>(std::forward<_Range>(__range), std::forward<_Tail>(__tail)...);
+  };
+  // clang-format on
+
+  return __range_adaptor_closure_t(std::__bind_back(__to_func, std::forward<_Args>(__args)...));
+}
+
+} // namespace ranges
+
+#endif // _LIBCPP_STD_VER >= 23
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP___RANGES_TO_H
index f9b2041..9ff8b67 100644 (file)
@@ -1718,6 +1718,7 @@ module std_private_ranges_subrange_fwd               [system] {
 }
 module std_private_ranges_take_view                  [system] { header "__ranges/take_view.h" }
 module std_private_ranges_take_while_view            [system] { header "__ranges/take_while_view.h" }
+module std_private_ranges_to                         [system] { header "__ranges/to.h" }
 module std_private_ranges_transform_view             [system] {
   header "__ranges/transform_view.h"
   export std_private_functional_bind_back
index 3bf9814..523f7cd 100644 (file)
@@ -138,6 +138,16 @@ namespace std::ranges {
     inline constexpr auto values = elements<1>;
   }
 
+  // [range.utility.conv], range conversions
+  template<class C, input_range R, class... Args> requires (!view<C>)
+    constexpr C to(R&& r, Args&&... args);     // Since C++23
+  template<template<class...> class C, input_range R, class... Args>
+    constexpr auto to(R&& r, Args&&... args);  // Since C++23
+  template<class C, class... Args> requires (!view<C>)
+    constexpr auto to(Args&&... args);         // Since C++23
+  template<template<class...> class C, class... Args>
+    constexpr auto to(Args&&... args);         // Since C++23
+
   // [range.empty], empty view
   template<class T>
     requires is_object_v<T>
@@ -391,6 +401,7 @@ namespace std {
 #include <__ranges/subrange.h>
 #include <__ranges/take_view.h>
 #include <__ranges/take_while_view.h>
+#include <__ranges/to.h>
 #include <__ranges/transform_view.h>
 #include <__ranges/view_interface.h>
 #include <__ranges/views.h>
index 87b7924..53d40e7 100644 (file)
@@ -162,7 +162,7 @@ __cpp_lib_ranges_repeat                                 202207L <ranges>
 __cpp_lib_ranges_slide                                  202202L <ranges>
 __cpp_lib_ranges_starts_ends_with                       202106L <algorithm>
 __cpp_lib_ranges_to_container                           202202L <deque> <forward_list> <list>
-                                                                <map> <priority_queue> <queue>
+                                                                <map> <queue> <ranges>
                                                                 <set> <stack> <string>
                                                                 <unordered_map> <unordered_set> <vector>
 __cpp_lib_ranges_zip                                    202110L <ranges> <tuple> <utility>
@@ -440,7 +440,7 @@ __cpp_lib_within_lifetime                               202306L <type_traits>
 # define __cpp_lib_ranges_repeat                        202207L
 // # define __cpp_lib_ranges_slide                         202202L
 // # define __cpp_lib_ranges_starts_ends_with              202106L
-// # define __cpp_lib_ranges_to_container                  202202L
+# define __cpp_lib_ranges_to_container                  202202L
 // # define __cpp_lib_ranges_zip                           202110L
 // # define __cpp_lib_reference_from_temporary             202202L
 // # define __cpp_lib_spanstream                           202106L
index 7e4c543..cf4c252 100644 (file)
@@ -95,7 +95,7 @@ export namespace std {
     using std::ranges::borrowed_subrange_t;
 
     // [range.utility.conv], range conversions
-    // using std::ranges::to;
+    using std::ranges::to;
 
     // [range.empty], empty view
     using std::ranges::empty_view;
diff --git a/libcxx/test/libcxx/ranges/range.utility/range.utility.conv/to.internal_constraints.verify.cpp b/libcxx/test/libcxx/ranges/range.utility/range.utility.conv/to.internal_constraints.verify.cpp
new file mode 100644 (file)
index 0000000..c65b07a
--- /dev/null
@@ -0,0 +1,94 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// Test that the "mandates" requirements on the given container are checked correctly using `static_assert`.
+
+#include <ranges>
+#include <vector>
+
+template <bool HasDefaultCtr = true, bool HasSingleArgCtr = true,
+          bool HasInsert = true, bool HasInsertWithRightSignature = true,
+          bool HasPushBack = true, bool HasPushBackWithRightSignature = true>
+struct Container {
+  using value_type = int;
+
+  int* begin() const { return nullptr; }
+  int* end() const { return nullptr; }
+
+  Container()
+  requires HasDefaultCtr = default;
+
+  Container(int)
+  requires HasSingleArgCtr {
+  }
+
+  int* insert(int*, int)
+  requires (HasInsert && HasInsertWithRightSignature) {
+    return nullptr;
+  }
+
+  int* insert()
+  requires (HasInsert && !HasInsertWithRightSignature) {
+    return nullptr;
+  }
+
+  void push_back(int)
+  requires (HasPushBack && HasPushBackWithRightSignature) {
+  }
+
+  void push_back()
+  requires (HasPushBack && !HasPushBackWithRightSignature) {
+  }
+
+};
+
+void test() {
+  using R = std::vector<int>;
+  R in = {1, 2, 3};
+
+  // Case 4 -- default-construct (or construct from the extra arguments) and insert.
+  { // All constraints satisfied.
+    using C = Container<>;
+    (void)std::ranges::to<C>(in);
+    (void)std::ranges::to<C>(in, 1);
+    (void)std::ranges::to<C>(in, 1.0);
+  }
+
+  { // No default constructor.
+    using C = Container</*HasDefaultCtr=*/false>;
+    (void)std::ranges::to<C>(in); //expected-error-re@*:* {{{{(static_assert|static assertion)}} failed{{.*}}ranges::to: unable to convert to the given container type}}
+  }
+
+  { // No single-argument constructor.
+    using C = Container</*HasDefaultCtr=*/true, /*HasSingleArgCtr=*/false>;
+    (void)std::ranges::to<C>(in, 1); //expected-error-re@*:* {{{{(static_assert|static assertion)}} failed{{.*}}ranges::to: unable to convert to the given container type}}
+  }
+
+  { // No `insert` and no `push_back`.
+    using C = Container</*HasDefaultCtr=*/true, /*HasSingleArgCtr=*/true,
+                        /*HasInsert=*/false, /*HasInsertWithRightSignature=*/false,
+                        /*HasPushBack=*/false, /*HasPushBackWithRightSignature=*/false>;
+    (void)std::ranges::to<C>(in); //expected-error-re@*:* {{{{(static_assert|static assertion)}} failed{{.*}}ranges::to: unable to convert to the given container type}}
+  }
+
+  { // No `push_back`, `insert` has a wrong signature.
+    using C = Container</*HasDefaultCtr=*/true, /*HasSingleArgCtr=*/true,
+                        /*HasInsert=*/true, /*HasInsertWithRightSignature=*/false,
+                        /*HasPushBack=*/false, /*HasPushBackWithRightSignature=*/false>;
+    (void)std::ranges::to<C>(in); //expected-error-re@*:* {{{{(static_assert|static assertion)}} failed{{.*}}ranges::to: unable to convert to the given container type}}
+  }
+
+  { // No `insert`, `push_back` has a wrong signature.
+    using C = Container</*HasDefaultCtr=*/true, /*HasSingleArgCtr=*/true,
+                        /*HasInsert=*/false, /*HasInsertWithRightSignature=*/false,
+                        /*HasPushBack=*/true, /*HasPushBackWithRightSignature=*/false>;
+    (void)std::ranges::to<C>(in); //expected-error-re@*:* {{{{(static_assert|static assertion)}} failed{{.*}}ranges::to: unable to convert to the given container type}}
+  }
+}
diff --git a/libcxx/test/libcxx/ranges/range.utility/range.utility.conv/to.nodiscard.verify.cpp b/libcxx/test/libcxx/ranges/range.utility/range.utility.conv/to.nodiscard.verify.cpp
new file mode 100644 (file)
index 0000000..b252c2f
--- /dev/null
@@ -0,0 +1,29 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// Test the libc++ extension that `std::ranges::to` is marked as [[nodiscard]].
+
+#include <ranges>
+#include <vector>
+
+void test() {
+  using R = std::vector<int>;
+  R in = {1, 2, 3};
+  std::allocator<int> alloc;
+
+  std::ranges::to<R>(in); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+  std::ranges::to<R>(in, alloc); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+  std::ranges::to<std::vector>(in); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+  std::ranges::to<std::vector>(in, alloc); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+  in | std::ranges::to<R>(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+  in | std::ranges::to<R>(alloc); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+  in | std::ranges::to<std::vector>(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+  in | std::ranges::to<std::vector>(alloc); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+}
diff --git a/libcxx/test/libcxx/ranges/range.utility/range.utility.conv/to.static_assert.verify.cpp b/libcxx/test/libcxx/ranges/range.utility/range.utility.conv/to.static_assert.verify.cpp
new file mode 100644 (file)
index 0000000..4da7886
--- /dev/null
@@ -0,0 +1,25 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// ADDITIONAL_COMPILE_FLAGS: -Wno-deprecated-volatile
+
+// Test that the "mandates" requirements on the given container are checked using `static_assert`.
+
+#include <ranges>
+#include <vector>
+
+void test() {
+  using R = std::vector<int>;
+  R in = {1, 2, 3};
+
+  (void)std::ranges::to<const R>(in); //expected-error-re@*:* {{{{(static_assert|static assertion)}} failed{{.*}}The target container cannot be const-qualified, please remove the const}}
+  (void)(in | std::ranges::to<const R>()); //expected-error-re@*:* {{{{(static_assert|static assertion)}} failed{{.*}}The target container cannot be const-qualified, please remove the const}}
+  (void)std::ranges::to<volatile R>(in); //expected-error-re@*:* {{{{(static_assert|static assertion)}} failed{{.*}}The target container cannot be volatile-qualified, please remove the volatile}}
+  (void)(in | std::ranges::to<volatile R>()); //expected-error-re@*:* {{{{(static_assert|static assertion)}} failed{{.*}}The target container cannot be volatile-qualified, please remove the volatile}}
+}
index 4a70a98..4a398e2 100644 (file)
 #   error "__cpp_lib_nonmember_container_access should have the value 201411L in c++23"
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should be defined in c++23"
-#   endif
-#   if __cpp_lib_ranges_to_container != 202202L
-#     error "__cpp_lib_ranges_to_container should have the value 202202L in c++23"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_ranges_to_container
+#   error "__cpp_lib_ranges_to_container should be defined in c++23"
+# endif
+# if __cpp_lib_ranges_to_container != 202202L
+#   error "__cpp_lib_ranges_to_container should have the value 202202L in c++23"
 # endif
 
 #elif TEST_STD_VER > 23
 #   error "__cpp_lib_nonmember_container_access should have the value 201411L in c++26"
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should be defined in c++26"
-#   endif
-#   if __cpp_lib_ranges_to_container != 202202L
-#     error "__cpp_lib_ranges_to_container should have the value 202202L in c++26"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_ranges_to_container
+#   error "__cpp_lib_ranges_to_container should be defined in c++26"
+# endif
+# if __cpp_lib_ranges_to_container != 202202L
+#   error "__cpp_lib_ranges_to_container should have the value 202202L in c++26"
 # endif
 
 #endif // TEST_STD_VER > 23
index f21cbbf..b163943 100644 (file)
 #   error "__cpp_lib_nonmember_container_access should have the value 201411L in c++23"
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should be defined in c++23"
-#   endif
-#   if __cpp_lib_ranges_to_container != 202202L
-#     error "__cpp_lib_ranges_to_container should have the value 202202L in c++23"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_ranges_to_container
+#   error "__cpp_lib_ranges_to_container should be defined in c++23"
+# endif
+# if __cpp_lib_ranges_to_container != 202202L
+#   error "__cpp_lib_ranges_to_container should have the value 202202L in c++23"
 # endif
 
 #elif TEST_STD_VER > 23
 #   error "__cpp_lib_nonmember_container_access should have the value 201411L in c++26"
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should be defined in c++26"
-#   endif
-#   if __cpp_lib_ranges_to_container != 202202L
-#     error "__cpp_lib_ranges_to_container should have the value 202202L in c++26"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_ranges_to_container
+#   error "__cpp_lib_ranges_to_container should be defined in c++26"
+# endif
+# if __cpp_lib_ranges_to_container != 202202L
+#   error "__cpp_lib_ranges_to_container should have the value 202202L in c++26"
 # endif
 
 #endif // TEST_STD_VER > 23
index df13674..48bff77 100644 (file)
 #   error "__cpp_lib_nonmember_container_access should have the value 201411L in c++23"
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should be defined in c++23"
-#   endif
-#   if __cpp_lib_ranges_to_container != 202202L
-#     error "__cpp_lib_ranges_to_container should have the value 202202L in c++23"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_ranges_to_container
+#   error "__cpp_lib_ranges_to_container should be defined in c++23"
+# endif
+# if __cpp_lib_ranges_to_container != 202202L
+#   error "__cpp_lib_ranges_to_container should have the value 202202L in c++23"
 # endif
 
 #elif TEST_STD_VER > 23
 #   error "__cpp_lib_nonmember_container_access should have the value 201411L in c++26"
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should be defined in c++26"
-#   endif
-#   if __cpp_lib_ranges_to_container != 202202L
-#     error "__cpp_lib_ranges_to_container should have the value 202202L in c++26"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_ranges_to_container
+#   error "__cpp_lib_ranges_to_container should be defined in c++26"
+# endif
+# if __cpp_lib_ranges_to_container != 202202L
+#   error "__cpp_lib_ranges_to_container should have the value 202202L in c++26"
 # endif
 
 #endif // TEST_STD_VER > 23
index 8599c54..2c17d2a 100644 (file)
 #   error "__cpp_lib_nonmember_container_access should have the value 201411L in c++23"
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should be defined in c++23"
-#   endif
-#   if __cpp_lib_ranges_to_container != 202202L
-#     error "__cpp_lib_ranges_to_container should have the value 202202L in c++23"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_ranges_to_container
+#   error "__cpp_lib_ranges_to_container should be defined in c++23"
+# endif
+# if __cpp_lib_ranges_to_container != 202202L
+#   error "__cpp_lib_ranges_to_container should have the value 202202L in c++23"
 # endif
 
 #elif TEST_STD_VER > 23
 #   error "__cpp_lib_nonmember_container_access should have the value 201411L in c++26"
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should be defined in c++26"
-#   endif
-#   if __cpp_lib_ranges_to_container != 202202L
-#     error "__cpp_lib_ranges_to_container should have the value 202202L in c++26"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_ranges_to_container
+#   error "__cpp_lib_ranges_to_container should be defined in c++26"
+# endif
+# if __cpp_lib_ranges_to_container != 202202L
+#   error "__cpp_lib_ranges_to_container should have the value 202202L in c++26"
 # endif
 
 #endif // TEST_STD_VER > 23
index 78801b8..fdedd27 100644 (file)
 #   error "__cpp_lib_adaptor_iterator_pair_constructor should have the value 202106L in c++23"
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should be defined in c++23"
-#   endif
-#   if __cpp_lib_ranges_to_container != 202202L
-#     error "__cpp_lib_ranges_to_container should have the value 202202L in c++23"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_ranges_to_container
+#   error "__cpp_lib_ranges_to_container should be defined in c++23"
+# endif
+# if __cpp_lib_ranges_to_container != 202202L
+#   error "__cpp_lib_ranges_to_container should have the value 202202L in c++23"
 # endif
 
 #elif TEST_STD_VER > 23
 #   error "__cpp_lib_adaptor_iterator_pair_constructor should have the value 202106L in c++26"
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should be defined in c++26"
-#   endif
-#   if __cpp_lib_ranges_to_container != 202202L
-#     error "__cpp_lib_ranges_to_container should have the value 202202L in c++26"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_ranges_to_container
+#   error "__cpp_lib_ranges_to_container should be defined in c++26"
+# endif
+# if __cpp_lib_ranges_to_container != 202202L
+#   error "__cpp_lib_ranges_to_container should have the value 202202L in c++26"
 # endif
 
 #endif // TEST_STD_VER > 23
index 3758c53..4a439ae 100644 (file)
 
 // Test the feature test macros defined by <ranges>
 
-/*  Constant                      Value
-    __cpp_lib_ranges              202207L [C++20]
-    __cpp_lib_ranges_as_rvalue    202207L [C++23]
-    __cpp_lib_ranges_chunk        202202L [C++23]
-    __cpp_lib_ranges_chunk_by     202202L [C++23]
-    __cpp_lib_ranges_join_with    202202L [C++23]
-    __cpp_lib_ranges_repeat       202207L [C++23]
-    __cpp_lib_ranges_slide        202202L [C++23]
-    __cpp_lib_ranges_zip          202110L [C++23]
+/*  Constant                         Value
+    __cpp_lib_ranges                 202207L [C++20]
+    __cpp_lib_ranges_as_rvalue       202207L [C++23]
+    __cpp_lib_ranges_chunk           202202L [C++23]
+    __cpp_lib_ranges_chunk_by        202202L [C++23]
+    __cpp_lib_ranges_join_with       202202L [C++23]
+    __cpp_lib_ranges_repeat          202207L [C++23]
+    __cpp_lib_ranges_slide           202202L [C++23]
+    __cpp_lib_ranges_to_container    202202L [C++23]
+    __cpp_lib_ranges_zip             202110L [C++23]
 */
 
 #include <ranges>
 #   error "__cpp_lib_ranges_slide should not be defined before c++23"
 # endif
 
+# ifdef __cpp_lib_ranges_to_container
+#   error "__cpp_lib_ranges_to_container should not be defined before c++23"
+# endif
+
 # ifdef __cpp_lib_ranges_zip
 #   error "__cpp_lib_ranges_zip should not be defined before c++23"
 # endif
 #   error "__cpp_lib_ranges_slide should not be defined before c++23"
 # endif
 
+# ifdef __cpp_lib_ranges_to_container
+#   error "__cpp_lib_ranges_to_container should not be defined before c++23"
+# endif
+
 # ifdef __cpp_lib_ranges_zip
 #   error "__cpp_lib_ranges_zip should not be defined before c++23"
 # endif
 #   error "__cpp_lib_ranges_slide should not be defined before c++23"
 # endif
 
+# ifdef __cpp_lib_ranges_to_container
+#   error "__cpp_lib_ranges_to_container should not be defined before c++23"
+# endif
+
 # ifdef __cpp_lib_ranges_zip
 #   error "__cpp_lib_ranges_zip should not be defined before c++23"
 # endif
 #   error "__cpp_lib_ranges_slide should not be defined before c++23"
 # endif
 
+# ifdef __cpp_lib_ranges_to_container
+#   error "__cpp_lib_ranges_to_container should not be defined before c++23"
+# endif
+
 # ifdef __cpp_lib_ranges_zip
 #   error "__cpp_lib_ranges_zip should not be defined before c++23"
 # endif
 #   endif
 # endif
 
+# ifndef __cpp_lib_ranges_to_container
+#   error "__cpp_lib_ranges_to_container should be defined in c++23"
+# endif
+# if __cpp_lib_ranges_to_container != 202202L
+#   error "__cpp_lib_ranges_to_container should have the value 202202L in c++23"
+# endif
+
 # if !defined(_LIBCPP_VERSION)
 #   ifndef __cpp_lib_ranges_zip
 #     error "__cpp_lib_ranges_zip should be defined in c++23"
 #   endif
 # endif
 
+# ifndef __cpp_lib_ranges_to_container
+#   error "__cpp_lib_ranges_to_container should be defined in c++26"
+# endif
+# if __cpp_lib_ranges_to_container != 202202L
+#   error "__cpp_lib_ranges_to_container should have the value 202202L in c++26"
+# endif
+
 # if !defined(_LIBCPP_VERSION)
 #   ifndef __cpp_lib_ranges_zip
 #     error "__cpp_lib_ranges_zip should be defined in c++26"
index 241b66b..271df95 100644 (file)
 #   error "__cpp_lib_nonmember_container_access should have the value 201411L in c++23"
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should be defined in c++23"
-#   endif
-#   if __cpp_lib_ranges_to_container != 202202L
-#     error "__cpp_lib_ranges_to_container should have the value 202202L in c++23"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_ranges_to_container
+#   error "__cpp_lib_ranges_to_container should be defined in c++23"
+# endif
+# if __cpp_lib_ranges_to_container != 202202L
+#   error "__cpp_lib_ranges_to_container should have the value 202202L in c++23"
 # endif
 
 #elif TEST_STD_VER > 23
 #   error "__cpp_lib_nonmember_container_access should have the value 201411L in c++26"
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should be defined in c++26"
-#   endif
-#   if __cpp_lib_ranges_to_container != 202202L
-#     error "__cpp_lib_ranges_to_container should have the value 202202L in c++26"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_ranges_to_container
+#   error "__cpp_lib_ranges_to_container should be defined in c++26"
+# endif
+# if __cpp_lib_ranges_to_container != 202202L
+#   error "__cpp_lib_ranges_to_container should have the value 202202L in c++26"
 # endif
 
 #endif // TEST_STD_VER > 23
index 373fcb4..cc5af8a 100644 (file)
 #   error "__cpp_lib_adaptor_iterator_pair_constructor should have the value 202106L in c++23"
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should be defined in c++23"
-#   endif
-#   if __cpp_lib_ranges_to_container != 202202L
-#     error "__cpp_lib_ranges_to_container should have the value 202202L in c++23"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_ranges_to_container
+#   error "__cpp_lib_ranges_to_container should be defined in c++23"
+# endif
+# if __cpp_lib_ranges_to_container != 202202L
+#   error "__cpp_lib_ranges_to_container should have the value 202202L in c++23"
 # endif
 
 #elif TEST_STD_VER > 23
 #   error "__cpp_lib_adaptor_iterator_pair_constructor should have the value 202106L in c++26"
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should be defined in c++26"
-#   endif
-#   if __cpp_lib_ranges_to_container != 202202L
-#     error "__cpp_lib_ranges_to_container should have the value 202202L in c++26"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_ranges_to_container
+#   error "__cpp_lib_ranges_to_container should be defined in c++26"
+# endif
+# if __cpp_lib_ranges_to_container != 202202L
+#   error "__cpp_lib_ranges_to_container should have the value 202202L in c++26"
 # endif
 
 #endif // TEST_STD_VER > 23
index 5902bdb..b5770f8 100644 (file)
 #   error "__cpp_lib_nonmember_container_access should have the value 201411L in c++23"
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should be defined in c++23"
-#   endif
-#   if __cpp_lib_ranges_to_container != 202202L
-#     error "__cpp_lib_ranges_to_container should have the value 202202L in c++23"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_ranges_to_container
+#   error "__cpp_lib_ranges_to_container should be defined in c++23"
+# endif
+# if __cpp_lib_ranges_to_container != 202202L
+#   error "__cpp_lib_ranges_to_container should have the value 202202L in c++23"
 # endif
 
 # ifndef __cpp_lib_starts_ends_with
 #   error "__cpp_lib_nonmember_container_access should have the value 201411L in c++26"
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should be defined in c++26"
-#   endif
-#   if __cpp_lib_ranges_to_container != 202202L
-#     error "__cpp_lib_ranges_to_container should have the value 202202L in c++26"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_ranges_to_container
+#   error "__cpp_lib_ranges_to_container should be defined in c++26"
+# endif
+# if __cpp_lib_ranges_to_container != 202202L
+#   error "__cpp_lib_ranges_to_container should have the value 202202L in c++26"
 # endif
 
 # ifndef __cpp_lib_starts_ends_with
index c3dfb78..743f89d 100644 (file)
 #   error "__cpp_lib_nonmember_container_access should have the value 201411L in c++23"
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should be defined in c++23"
-#   endif
-#   if __cpp_lib_ranges_to_container != 202202L
-#     error "__cpp_lib_ranges_to_container should have the value 202202L in c++23"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_ranges_to_container
+#   error "__cpp_lib_ranges_to_container should be defined in c++23"
+# endif
+# if __cpp_lib_ranges_to_container != 202202L
+#   error "__cpp_lib_ranges_to_container should have the value 202202L in c++23"
 # endif
 
 # ifndef __cpp_lib_unordered_map_try_emplace
 #   error "__cpp_lib_nonmember_container_access should have the value 201411L in c++26"
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should be defined in c++26"
-#   endif
-#   if __cpp_lib_ranges_to_container != 202202L
-#     error "__cpp_lib_ranges_to_container should have the value 202202L in c++26"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_ranges_to_container
+#   error "__cpp_lib_ranges_to_container should be defined in c++26"
+# endif
+# if __cpp_lib_ranges_to_container != 202202L
+#   error "__cpp_lib_ranges_to_container should have the value 202202L in c++26"
 # endif
 
 # ifndef __cpp_lib_unordered_map_try_emplace
index d215aec..a0947e9 100644 (file)
 #   error "__cpp_lib_nonmember_container_access should have the value 201411L in c++23"
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should be defined in c++23"
-#   endif
-#   if __cpp_lib_ranges_to_container != 202202L
-#     error "__cpp_lib_ranges_to_container should have the value 202202L in c++23"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_ranges_to_container
+#   error "__cpp_lib_ranges_to_container should be defined in c++23"
+# endif
+# if __cpp_lib_ranges_to_container != 202202L
+#   error "__cpp_lib_ranges_to_container should have the value 202202L in c++23"
 # endif
 
 #elif TEST_STD_VER > 23
 #   error "__cpp_lib_nonmember_container_access should have the value 201411L in c++26"
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should be defined in c++26"
-#   endif
-#   if __cpp_lib_ranges_to_container != 202202L
-#     error "__cpp_lib_ranges_to_container should have the value 202202L in c++26"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_ranges_to_container
+#   error "__cpp_lib_ranges_to_container should be defined in c++26"
+# endif
+# if __cpp_lib_ranges_to_container != 202202L
+#   error "__cpp_lib_ranges_to_container should have the value 202202L in c++26"
 # endif
 
 #endif // TEST_STD_VER > 23
index b812671..6eee936 100644 (file)
 #   error "__cpp_lib_nonmember_container_access should have the value 201411L in c++23"
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should be defined in c++23"
-#   endif
-#   if __cpp_lib_ranges_to_container != 202202L
-#     error "__cpp_lib_ranges_to_container should have the value 202202L in c++23"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_ranges_to_container
+#   error "__cpp_lib_ranges_to_container should be defined in c++23"
+# endif
+# if __cpp_lib_ranges_to_container != 202202L
+#   error "__cpp_lib_ranges_to_container should have the value 202202L in c++23"
 # endif
 
 #elif TEST_STD_VER > 23
 #   error "__cpp_lib_nonmember_container_access should have the value 201411L in c++26"
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should be defined in c++26"
-#   endif
-#   if __cpp_lib_ranges_to_container != 202202L
-#     error "__cpp_lib_ranges_to_container should have the value 202202L in c++26"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_ranges_to_container
+#   error "__cpp_lib_ranges_to_container should be defined in c++26"
+# endif
+# if __cpp_lib_ranges_to_container != 202202L
+#   error "__cpp_lib_ranges_to_container should have the value 202202L in c++26"
 # endif
 
 #endif // TEST_STD_VER > 23
index 94d2838..ec226f3 100644 (file)
 #   endif
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should be defined in c++23"
-#   endif
-#   if __cpp_lib_ranges_to_container != 202202L
-#     error "__cpp_lib_ranges_to_container should have the value 202202L in c++23"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_ranges_to_container
+#   error "__cpp_lib_ranges_to_container should be defined in c++23"
+# endif
+# if __cpp_lib_ranges_to_container != 202202L
+#   error "__cpp_lib_ranges_to_container should have the value 202202L in c++23"
 # endif
 
 # if !defined(_LIBCPP_VERSION)
 #   endif
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should be defined in c++26"
-#   endif
-#   if __cpp_lib_ranges_to_container != 202202L
-#     error "__cpp_lib_ranges_to_container should have the value 202202L in c++26"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_ranges_to_container
-#     error "__cpp_lib_ranges_to_container should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_ranges_to_container
+#   error "__cpp_lib_ranges_to_container should be defined in c++26"
+# endif
+# if __cpp_lib_ranges_to_container != 202202L
+#   error "__cpp_lib_ranges_to_container should have the value 202202L in c++26"
 # endif
 
 # if !defined(_LIBCPP_VERSION)
diff --git a/libcxx/test/std/ranges/range.utility/range.utility.conv/container.h b/libcxx/test/std/ranges/range.utility/range.utility.conv/container.h
new file mode 100644 (file)
index 0000000..fafccac
--- /dev/null
@@ -0,0 +1,144 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef RANGES_RANGE_UTILITY_RANGE_UTILITY_CONV_CONTAINER_H
+#define RANGES_RANGE_UTILITY_RANGE_UTILITY_CONV_CONTAINER_H
+
+#include <algorithm>
+#include <concepts>
+#include <cstddef>
+
+enum class CtrChoice { Invalid, DefaultCtrAndInsert, BeginEndPair, FromRangeT, DirectCtr };
+
+enum class InserterChoice { Invalid, Insert, PushBack };
+
+// Allows checking that `ranges::to` correctly follows the order of priority of different constructors -- e.g., if
+// 3 constructors are available, the `from_range_t` constructor is chosen in favor of the constructor taking two
+// iterators, etc.
+template <class ElementType, CtrChoice Rank, InserterChoice Inserter = InserterChoice::Insert, bool CanReserve = false>
+struct Container {
+  CtrChoice ctr_choice           = CtrChoice::Invalid;
+  InserterChoice inserter_choice = InserterChoice::Invalid;
+  bool called_reserve            = false;
+
+  int extra_arg1  = 0;
+  char extra_arg2 = 0;
+
+  using value_type              = ElementType;
+  static constexpr int Capacity = 8;
+  int size_                     = 0;
+  ElementType buffer_[Capacity] = {};
+
+  // Case 1 -- construct directly from the range.
+
+  constexpr explicit Container(std::ranges::input_range auto&& in)
+    requires(Rank >= CtrChoice::DirectCtr)
+      : ctr_choice(CtrChoice::DirectCtr), size_(std::ranges::size(in)) {
+    std::ranges::copy(in, begin());
+  }
+
+  // Check that `ranges::to` can also pass extra parameters.
+  constexpr explicit Container(std::ranges::input_range auto&& in, int arg1, char arg2)
+    requires(Rank >= CtrChoice::DirectCtr)
+      : Container(in) {
+    extra_arg1 = arg1;
+    extra_arg2 = arg2;
+  }
+
+  // Case 2 -- use `from_range_t` constructor.
+
+  constexpr Container(std::from_range_t, std::ranges::input_range auto&& in)
+    requires(Rank >= CtrChoice::FromRangeT)
+      : ctr_choice(CtrChoice::FromRangeT), size_(std::ranges::size(in)) {
+    std::ranges::copy(in, begin());
+  }
+
+  constexpr Container(std::from_range_t, std::ranges::input_range auto&& in, int arg1, char arg2)
+    requires(Rank >= CtrChoice::FromRangeT)
+      : Container(std::from_range, in) {
+    extra_arg1 = arg1;
+    extra_arg2 = arg2;
+  }
+
+  // Case 3 -- use begin-end pair.
+
+  template <class Iter>
+  constexpr Container(Iter b, Iter e)
+    requires(Rank >= CtrChoice::BeginEndPair)
+      : ctr_choice(CtrChoice::BeginEndPair), size_(e - b) {
+    std::ranges::copy(b, e, begin());
+  }
+
+  template <class Iter>
+  constexpr Container(Iter b, Iter e, int arg1, char arg2)
+    requires(Rank >= CtrChoice::BeginEndPair)
+      : Container(b, e) {
+    extra_arg1 = arg1;
+    extra_arg2 = arg2;
+  }
+
+  // Case 4 -- default-construct and insert, reserving the size if possible.
+
+  constexpr Container()
+    requires(Rank >= CtrChoice::DefaultCtrAndInsert)
+      : ctr_choice(CtrChoice::DefaultCtrAndInsert) {}
+
+  constexpr Container(int arg1, char arg2)
+    requires(Rank >= CtrChoice::DefaultCtrAndInsert)
+      : ctr_choice(CtrChoice::DefaultCtrAndInsert), extra_arg1(arg1), extra_arg2(arg2) {}
+
+  constexpr ElementType* begin() { return buffer_; }
+  constexpr ElementType* end() { return buffer_ + size_; }
+  constexpr std::size_t size() const { return size_; }
+
+  template <class T>
+  constexpr void push_back(T val)
+    requires(Inserter >= InserterChoice::PushBack)
+  {
+    inserter_choice = InserterChoice::PushBack;
+    buffer_[size_]  = val;
+    ++size_;
+  }
+
+  template <class T>
+  constexpr ElementType* insert(ElementType* where, T val)
+    requires(Inserter >= InserterChoice::Insert)
+  {
+    assert(size() + 1 <= Capacity);
+
+    inserter_choice = InserterChoice::Insert;
+
+    std::shift_right(where, end(), 1);
+    *where = val;
+    ++size_;
+
+    return where;
+  }
+
+  constexpr void reserve(size_t)
+    requires CanReserve
+  {
+    called_reserve = true;
+  }
+
+  constexpr std::size_t capacity() const
+    requires CanReserve
+  {
+    return Capacity;
+  }
+
+  constexpr std::size_t max_size() const
+    requires CanReserve
+  {
+    return Capacity;
+  }
+
+  friend constexpr bool operator==(const Container&, const Container&) = default;
+};
+
+#endif // RANGES_RANGE_UTILITY_RANGE_UTILITY_CONV_CONTAINER_H
diff --git a/libcxx/test/std/ranges/range.utility/range.utility.conv/to.pass.cpp b/libcxx/test/std/ranges/range.utility/range.utility.conv/to.pass.cpp
new file mode 100644 (file)
index 0000000..75f55bc
--- /dev/null
@@ -0,0 +1,574 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// template<class C, input_range R, class... Args> requires (!view<C>)
+//   constexpr C to(R&& r, Args&&... args);     // Since C++23
+
+#include <ranges>
+
+#include <algorithm>
+#include <array>
+#include <cassert>
+#include <vector>
+#include "container.h"
+#include "test_iterators.h"
+#include "test_range.h"
+
+template <class Container, class Range, class... Args>
+concept HasTo = requires (Range&& range, Args ...args) {
+  std::ranges::to<Container>(std::forward<Range>(range), std::forward<Args>(args)...);
+};
+
+struct InputRange {
+  int x = 0;
+  constexpr cpp20_input_iterator<int*> begin() {
+    return cpp20_input_iterator<int*>(&x);
+  }
+  constexpr sentinel_wrapper<cpp20_input_iterator<int*>> end() {
+    return sentinel_wrapper<cpp20_input_iterator<int*>>(begin());
+  }
+};
+static_assert(std::ranges::input_range<InputRange>);
+
+struct common_cpp20_input_iterator {
+  using value_type = int;
+  using difference_type = long long;
+  using iterator_concept = std::input_iterator_tag;
+  // Deliberately not defining `iterator_category` to make sure this class satisfies the `input_iterator` concept but
+  // would fail `derived_from<iterator_category, input_iterator_tag>`.
+
+  int x = 0;
+
+  // Copyable so that it can be used as a sentinel against itself.
+  constexpr decltype(auto) operator*() const { return x; }
+  constexpr common_cpp20_input_iterator& operator++() { return *this; }
+  constexpr void operator++(int) {}
+  constexpr friend bool operator==(common_cpp20_input_iterator, common_cpp20_input_iterator) { return true; }
+};
+static_assert(std::input_iterator<common_cpp20_input_iterator>);
+static_assert(std::sentinel_for<common_cpp20_input_iterator, common_cpp20_input_iterator>);
+template <class T>
+concept HasIteratorCategory = requires {
+  typename std::iterator_traits<T>::iterator_category;
+};
+static_assert(!HasIteratorCategory<common_cpp20_input_iterator>);
+
+struct CommonInputRange {
+  int x = 0;
+  constexpr common_cpp20_input_iterator begin() { return {}; }
+  constexpr common_cpp20_input_iterator end() { return begin(); }
+};
+static_assert(std::ranges::input_range<CommonInputRange>);
+static_assert(std::ranges::common_range<CommonInputRange>);
+
+struct CommonRange {
+  int x = 0;
+  constexpr forward_iterator<int*> begin() {
+    return forward_iterator<int*>(&x);
+  }
+  constexpr forward_iterator<int*> end() {
+    return begin();
+  }
+};
+static_assert(std::ranges::input_range<CommonRange>);
+static_assert(std::ranges::common_range<CommonRange>);
+
+struct NonCommonRange {
+  int x = 0;
+  constexpr forward_iterator<int*> begin() {
+    return forward_iterator<int*>(&x);
+  }
+  constexpr sentinel_wrapper<forward_iterator<int*>> end() {
+    return sentinel_wrapper<forward_iterator<int*>>(begin());
+  }
+};
+static_assert(std::ranges::input_range<NonCommonRange>);
+static_assert(!std::ranges::common_range<NonCommonRange>);
+static_assert(std::derived_from<
+    typename std::iterator_traits<std::ranges::iterator_t<NonCommonRange>>::iterator_category,
+    std::input_iterator_tag>);
+
+using ContainerT = int;
+static_assert(!std::ranges::view<ContainerT>);
+static_assert(HasTo<ContainerT, InputRange>);
+static_assert(!HasTo<test_view<forward_iterator>, InputRange>);
+
+// Note: it's not possible to check the `input_range` constraint because if it's not satisfied, the pipe adaptor
+// overload hijacks the call (it takes unconstrained variadic arguments).
+
+// Check the exact constraints for each one of the cases inside `ranges::to`.
+
+struct Empty {};
+
+struct Fallback {
+  using value_type = int;
+
+  CtrChoice ctr_choice = CtrChoice::Invalid;
+  int x = 0;
+
+  constexpr Fallback() : ctr_choice(CtrChoice::DefaultCtrAndInsert) {}
+  constexpr Fallback(Empty) : ctr_choice(CtrChoice::DefaultCtrAndInsert) {}
+
+  constexpr void push_back(value_type) {}
+  constexpr value_type* begin() { return &x; }
+  constexpr value_type* end() { return &x; }
+};
+
+struct CtrDirectOrFallback : Fallback {
+  using Fallback::Fallback;
+  constexpr CtrDirectOrFallback(InputRange&&, int = 0) { ctr_choice = CtrChoice::DirectCtr; }
+};
+
+struct CtrFromRangeTOrFallback : Fallback {
+  using Fallback::Fallback;
+  constexpr CtrFromRangeTOrFallback(std::from_range_t, InputRange&&, int = 0) { ctr_choice = CtrChoice::FromRangeT; }
+};
+
+struct CtrBeginEndPairOrFallback : Fallback {
+  using Fallback::Fallback;
+  template <class Iter>
+  constexpr CtrBeginEndPairOrFallback(Iter, Iter, int = 0) { ctr_choice = CtrChoice::BeginEndPair; }
+};
+
+template <bool HasSize>
+struct MaybeSizedRange {
+  int x = 0;
+  constexpr forward_iterator<int*> begin() { return forward_iterator<int*>(&x); }
+  constexpr forward_iterator<int*> end() { return begin(); }
+
+  constexpr std::size_t size() const
+  requires HasSize {
+    return 0;
+  }
+};
+static_assert(std::ranges::sized_range<MaybeSizedRange<true>>);
+static_assert(!std::ranges::sized_range<MaybeSizedRange<false>>);
+
+template <bool HasCapacity = true, bool CapacityReturnsSizeT = true,
+          bool HasMaxSize = true, bool MaxSizeReturnsSizeT = true>
+struct Reservable : Fallback {
+  bool reserve_called = false;
+
+  using Fallback::Fallback;
+
+  constexpr std::size_t capacity() const
+  requires (HasCapacity && CapacityReturnsSizeT) {
+    return 0;
+  }
+  constexpr int capacity() const
+  requires (HasCapacity && !CapacityReturnsSizeT) {
+    return 0;
+  }
+
+  constexpr std::size_t max_size() const
+  requires (HasMaxSize && MaxSizeReturnsSizeT) {
+    return 0;
+  }
+  constexpr int max_size() const
+  requires (HasMaxSize && !MaxSizeReturnsSizeT) {
+    return 0;
+  }
+
+  constexpr void reserve(std::size_t) {
+    reserve_called = true;
+  }
+};
+static_assert(std::ranges::__reservable_container<Reservable<>>);
+
+constexpr void test_constraints() {
+  { // Case 1 -- construct directly from the range.
+    { // (range)
+      auto result = std::ranges::to<CtrDirectOrFallback>(InputRange());
+      assert(result.ctr_choice == CtrChoice::DirectCtr);
+    }
+
+    { // (range, arg)
+      auto result = std::ranges::to<CtrDirectOrFallback>(InputRange(), 1);
+      assert(result.ctr_choice == CtrChoice::DirectCtr);
+    }
+
+    { // (range, convertible-to-arg)
+      auto result = std::ranges::to<CtrDirectOrFallback>(InputRange(), 1.0);
+      assert(result.ctr_choice == CtrChoice::DirectCtr);
+    }
+
+    { // (range, BAD_arg)
+      auto result = std::ranges::to<CtrDirectOrFallback>(InputRange(), Empty());
+      assert(result.ctr_choice == CtrChoice::DefaultCtrAndInsert);
+    }
+  }
+
+  { // Case 2 -- construct using the `from_range_t` tagged constructor.
+    { // (range)
+      auto result = std::ranges::to<CtrFromRangeTOrFallback>(InputRange());
+      assert(result.ctr_choice == CtrChoice::FromRangeT);
+    }
+
+    { // (range, arg)
+      auto result = std::ranges::to<CtrFromRangeTOrFallback>(InputRange(), 1);
+      assert(result.ctr_choice == CtrChoice::FromRangeT);
+    }
+
+    { // (range, convertible-to-arg)
+      auto result = std::ranges::to<CtrFromRangeTOrFallback>(InputRange(), 1.0);
+      assert(result.ctr_choice == CtrChoice::FromRangeT);
+    }
+
+    { // (range, BAD_arg)
+      auto result = std::ranges::to<CtrFromRangeTOrFallback>(InputRange(), Empty());
+      assert(result.ctr_choice == CtrChoice::DefaultCtrAndInsert);
+    }
+  }
+
+  { // Case 3 -- construct from a begin-end iterator pair.
+    { // (range)
+      auto result = std::ranges::to<CtrBeginEndPairOrFallback>(CommonRange());
+      assert(result.ctr_choice == CtrChoice::BeginEndPair);
+    }
+
+    { // (range, arg)
+      auto result = std::ranges::to<CtrBeginEndPairOrFallback>(CommonRange(), 1);
+      assert(result.ctr_choice == CtrChoice::BeginEndPair);
+    }
+
+    { // (range, convertible-to-arg)
+      auto result = std::ranges::to<CtrBeginEndPairOrFallback>(CommonRange(), 1.0);
+      assert(result.ctr_choice == CtrChoice::BeginEndPair);
+    }
+
+    { // (BAD_range) -- not a common range.
+      auto result = std::ranges::to<CtrBeginEndPairOrFallback>(NonCommonRange());
+      assert(result.ctr_choice == CtrChoice::DefaultCtrAndInsert);
+    }
+
+    { // (BAD_range) -- iterator type not derived from `input_iterator_tag`.
+      auto result = std::ranges::to<CtrBeginEndPairOrFallback>(CommonInputRange());
+      assert(result.ctr_choice == CtrChoice::DefaultCtrAndInsert);
+    }
+
+    { // (range, BAD_arg)
+      auto result = std::ranges::to<CtrBeginEndPairOrFallback>(CommonRange(), Empty());
+      assert(result.ctr_choice == CtrChoice::DefaultCtrAndInsert);
+    }
+  }
+
+  { // Case 4 -- default-construct (or construct from the extra arguments) and insert, reserving the size if possible.
+    // Note: it's not possible to check the constraints on the default constructor using this approach because there is
+    // nothing to fall back to -- the call will result in a hard error.
+    // However, it's possible to check the constraints on reserving the capacity.
+
+    { // All constraints satisfied.
+      using C = Reservable<>;
+      auto result = std::ranges::to<C>(MaybeSizedRange<true>());
+      assert(result.reserve_called);
+    }
+
+    { // !sized_range
+      using C = Reservable<>;
+      auto result = std::ranges::to<C>(MaybeSizedRange<false>());
+      assert(!result.reserve_called);
+    }
+
+    { // Missing `capacity`.
+      using C = Reservable</*HasCapacity=*/false>;
+      auto result = std::ranges::to<C>(MaybeSizedRange<true>());
+      assert(!result.reserve_called);
+    }
+
+    { // `capacity` doesn't return `size_type`.
+      using C = Reservable</*HasCapacity=*/true, /*CapacityReturnsSizeT=*/false>;
+      auto result = std::ranges::to<C>(MaybeSizedRange<true>());
+      assert(!result.reserve_called);
+    }
+
+    { // Missing `max_size`.
+      using C = Reservable</*HasCapacity=*/true, /*CapacityReturnsSizeT=*/true, /*HasMaxSize=*/false>;
+      auto result = std::ranges::to<C>(MaybeSizedRange<true>());
+      assert(!result.reserve_called);
+    }
+
+    { // `max_size` doesn't return `size_type`.
+      using C = Reservable<
+        /*HasCapacity=*/true, /*CapacityReturnsSizeT=*/true, /*HasMaxSize=*/true, /*MaxSizeReturnsSizeT=*/false>;
+      auto result = std::ranges::to<C>(MaybeSizedRange<true>());
+      assert(!result.reserve_called);
+    }
+  }
+}
+
+constexpr void test_ctr_choice_order() {
+  std::array in = {1, 2, 3, 4, 5};
+  int arg1 = 42;
+  char arg2 = 'a';
+
+  { // Case 1 -- construct directly from the given range.
+    {
+      using C = Container<int, CtrChoice::DirectCtr>;
+      std::same_as<C> decltype(auto) result = std::ranges::to<C>(in);
+
+      assert(result.ctr_choice == CtrChoice::DirectCtr);
+      assert(std::ranges::equal(result, in));
+      assert((in | std::ranges::to<C>()) == result);
+      auto closure = std::ranges::to<C>();
+      assert((in | closure) == result);
+    }
+
+    { // Extra arguments.
+      using C = Container<int, CtrChoice::DirectCtr>;
+      std::same_as<C> decltype(auto) result = std::ranges::to<C>(in, arg1, arg2);
+
+      assert(result.ctr_choice == CtrChoice::DirectCtr);
+      assert(std::ranges::equal(result, in));
+      assert(result.extra_arg1 == arg1);
+      assert(result.extra_arg2 == arg2);
+      assert((in | std::ranges::to<C>(arg1, arg2)) == result);
+      auto closure = std::ranges::to<C>(arg1, arg2);
+      assert((in | closure) == result);
+    }
+  }
+
+  { // Case 2 -- construct using the `from_range_t` tag.
+    {
+      using C = Container<int, CtrChoice::FromRangeT>;
+      std::same_as<C> decltype(auto) result = std::ranges::to<C>(in);
+
+      assert(result.ctr_choice == CtrChoice::FromRangeT);
+      assert(std::ranges::equal(result, in));
+      assert((in | std::ranges::to<C>()) == result);
+      auto closure = std::ranges::to<C>();
+      assert((in | closure) == result);
+    }
+
+    { // Extra arguments.
+      using C = Container<int, CtrChoice::FromRangeT>;
+      std::same_as<C> decltype(auto) result = std::ranges::to<C>(in, arg1, arg2);
+
+      assert(result.ctr_choice == CtrChoice::FromRangeT);
+      assert(std::ranges::equal(result, in));
+      assert(result.extra_arg1 == arg1);
+      assert(result.extra_arg2 == arg2);
+      assert((in | std::ranges::to<C>(arg1, arg2)) == result);
+      auto closure = std::ranges::to<C>(arg1, arg2);
+      assert((in | closure) == result);
+    }
+  }
+
+  { // Case 3 -- construct from a begin-end pair.
+    {
+      using C = Container<int, CtrChoice::BeginEndPair>;
+      std::same_as<C> decltype(auto) result = std::ranges::to<C>(in);
+
+      assert(result.ctr_choice == CtrChoice::BeginEndPair);
+      assert(std::ranges::equal(result, in));
+      assert((in | std::ranges::to<C>()) == result);
+      auto closure = std::ranges::to<C>();
+      assert((in | closure) == result);
+    }
+
+    { // Extra arguments.
+      using C = Container<int, CtrChoice::BeginEndPair>;
+      std::same_as<C> decltype(auto) result = std::ranges::to<C>(in, arg1, arg2);
+
+      assert(result.ctr_choice == CtrChoice::BeginEndPair);
+      assert(std::ranges::equal(result, in));
+      assert(result.extra_arg1 == arg1);
+      assert(result.extra_arg2 == arg2);
+      assert((in | std::ranges::to<C>(arg1, arg2)) == result);
+      auto closure = std::ranges::to<C>(arg1, arg2);
+      assert((in | closure) == result);
+    }
+  }
+
+  { // Case 4 -- default-construct then insert elements.
+    {
+      using C = Container<int, CtrChoice::DefaultCtrAndInsert, InserterChoice::Insert, /*CanReserve=*/false>;
+      std::same_as<C> decltype(auto) result = std::ranges::to<C>(in);
+
+      assert(result.ctr_choice == CtrChoice::DefaultCtrAndInsert);
+      assert(result.inserter_choice == InserterChoice::Insert);
+      assert(std::ranges::equal(result, in));
+      assert(!result.called_reserve);
+      assert((in | std::ranges::to<C>()) == result);
+      auto closure = std::ranges::to<C>();
+      assert((in | closure) == result);
+    }
+
+    {
+      using C = Container<int, CtrChoice::DefaultCtrAndInsert, InserterChoice::Insert, /*CanReserve=*/true>;
+      std::same_as<C> decltype(auto) result = std::ranges::to<C>(in);
+
+      assert(result.ctr_choice == CtrChoice::DefaultCtrAndInsert);
+      assert(result.inserter_choice == InserterChoice::Insert);
+      assert(std::ranges::equal(result, in));
+      assert(result.called_reserve);
+      assert((in | std::ranges::to<C>()) == result);
+      auto closure = std::ranges::to<C>();
+      assert((in | closure) == result);
+    }
+
+    {
+      using C = Container<int, CtrChoice::DefaultCtrAndInsert, InserterChoice::PushBack, /*CanReserve=*/false>;
+      std::same_as<C> decltype(auto) result = std::ranges::to<C>(in);
+
+      assert(result.ctr_choice == CtrChoice::DefaultCtrAndInsert);
+      assert(result.inserter_choice == InserterChoice::PushBack);
+      assert(std::ranges::equal(result, in));
+      assert(!result.called_reserve);
+      assert((in | std::ranges::to<C>()) == result);
+      auto closure = std::ranges::to<C>();
+      assert((in | closure) == result);
+    }
+
+    {
+      using C = Container<int, CtrChoice::DefaultCtrAndInsert, InserterChoice::PushBack, /*CanReserve=*/true>;
+      std::same_as<C> decltype(auto) result = std::ranges::to<C>(in);
+
+      assert(result.ctr_choice == CtrChoice::DefaultCtrAndInsert);
+      assert(result.inserter_choice == InserterChoice::PushBack);
+      assert(std::ranges::equal(result, in));
+      assert(result.called_reserve);
+      assert((in | std::ranges::to<C>()) == result);
+      auto closure = std::ranges::to<C>();
+      assert((in | closure) == result);
+    }
+
+    { // Extra arguments.
+      using C = Container<int, CtrChoice::DefaultCtrAndInsert, InserterChoice::Insert, /*CanReserve=*/false>;
+      std::same_as<C> decltype(auto) result = std::ranges::to<C>(in, arg1, arg2);
+
+      assert(result.ctr_choice == CtrChoice::DefaultCtrAndInsert);
+      assert(result.inserter_choice == InserterChoice::Insert);
+      assert(std::ranges::equal(result, in));
+      assert(!result.called_reserve);
+      assert(result.extra_arg1 == arg1);
+      assert(result.extra_arg2 == arg2);
+      assert((in | std::ranges::to<C>(arg1, arg2)) == result);
+      auto closure = std::ranges::to<C>(arg1, arg2);
+      assert((in | closure) == result);
+    }
+  }
+}
+
+template <CtrChoice Rank>
+struct NotARange {
+  using value_type = int;
+
+  constexpr NotARange(std::ranges::input_range auto&&)
+  requires (Rank >= CtrChoice::DirectCtr)
+  {}
+
+  constexpr NotARange(std::from_range_t, std::ranges::input_range auto&&)
+  requires (Rank >= CtrChoice::FromRangeT)
+  {}
+
+  template <class Iter>
+  constexpr NotARange(Iter, Iter)
+  requires (Rank >= CtrChoice::BeginEndPair)
+  {}
+
+  constexpr NotARange()
+  requires (Rank >= CtrChoice::DefaultCtrAndInsert)
+  = default;
+
+  constexpr void push_back(int) {}
+};
+
+static_assert(!std::ranges::range<NotARange<CtrChoice::DirectCtr>>);
+
+constexpr void test_lwg_3785() {
+  // Test LWG 3785 ("`ranges::to` is over-constrained on the destination type being a range") -- make sure it's possible
+  // to convert the given input range to a non-range type.
+  std::array in = {1, 2, 3, 4, 5};
+
+  {
+    using C = NotARange<CtrChoice::DirectCtr>;
+    [[maybe_unused]] std::same_as<C> decltype(auto) result = std::ranges::to<C>(in);
+  }
+
+  {
+    using C = NotARange<CtrChoice::FromRangeT>;
+    [[maybe_unused]] std::same_as<C> decltype(auto) result = std::ranges::to<C>(in);
+  }
+
+  {
+    using C = NotARange<CtrChoice::BeginEndPair>;
+    [[maybe_unused]] std::same_as<C> decltype(auto) result = std::ranges::to<C>(in);
+  }
+
+  {
+    using C = NotARange<CtrChoice::DefaultCtrAndInsert>;
+    [[maybe_unused]] std::same_as<C> decltype(auto) result = std::ranges::to<C>(in);
+  }
+}
+
+constexpr void test_recursive() {
+  using C1 = Container<int, CtrChoice::DirectCtr>;
+  using C2 = Container<C1, CtrChoice::FromRangeT>;
+  using C3 = Container<C2, CtrChoice::BeginEndPair>;
+  using C4 = Container<C3, CtrChoice::DefaultCtrAndInsert, InserterChoice::PushBack>;
+  using A1 = std::array<int, 4>;
+  using A2 = std::array<A1, 3>;
+  using A3 = std::array<A2, 2>;
+  using A4 = std::array<A3, 2>;
+
+  A4 in = {};
+  { // Fill the nested array with incremental values.
+    int x = 0;
+    for (auto& a3 : in) {
+      for (auto& a2 : a3) {
+        for (auto& a1 : a2) {
+          for (int& el : a1) {
+            el = x++;
+          }
+        }
+      }
+    }
+  }
+
+  std::same_as<C4> decltype(auto) result = std::ranges::to<C4>(in);
+
+  assert(result.ctr_choice == CtrChoice::DefaultCtrAndInsert);
+
+  int expected_value = 0;
+  for (auto& c3 : result) {
+    assert(c3.ctr_choice == CtrChoice::BeginEndPair);
+
+    for (auto& c2 : c3) {
+      assert(c2.ctr_choice == CtrChoice::FromRangeT);
+
+      for (auto& c1 : c2) {
+        assert(c1.ctr_choice == CtrChoice::DirectCtr);
+
+        for (int el : c1) {
+          assert(el == expected_value);
+          ++expected_value;
+        }
+      }
+    }
+  }
+
+  assert((in | std::ranges::to<C4>()) == result);
+}
+
+constexpr bool test() {
+  test_constraints();
+  test_ctr_choice_order();
+  test_lwg_3785();
+  test_recursive();
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.utility/range.utility.conv/to_deduction.pass.cpp b/libcxx/test/std/ranges/range.utility/range.utility.conv/to_deduction.pass.cpp
new file mode 100644 (file)
index 0000000..f84cedb
--- /dev/null
@@ -0,0 +1,135 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// There is a bug in older versions of Clang that causes trouble with constraints in classes like
+// `ContainerWithDirectCtr`.
+// XFAIL: clang-16, apple-clang-15
+
+// template<template<class...> class C, input_range R, class... Args>
+//   constexpr auto to(R&& r, Args&&... args);  // Since C++23
+
+#include <ranges>
+
+#include <algorithm>
+#include <array>
+#include <cassert>
+#include "container.h"
+
+template <class ElementType>
+struct ContainerWithDirectCtr : Container<ElementType, CtrChoice::DirectCtr> {
+  using Container<ElementType, CtrChoice::DirectCtr>::Container;
+};
+
+template <std::ranges::input_range Range>
+ContainerWithDirectCtr(Range&&) -> ContainerWithDirectCtr<std::ranges::range_value_t<Range>>;
+
+template <std::ranges::input_range Range>
+ContainerWithDirectCtr(Range&&, int, char) -> ContainerWithDirectCtr<std::ranges::range_value_t<Range>>;
+
+template <class ElementType>
+struct ContainerWithFromRangeT : Container<ElementType, CtrChoice::FromRangeT> {
+  using Container<ElementType, CtrChoice::FromRangeT>::Container;
+};
+
+template <std::ranges::input_range Range>
+ContainerWithFromRangeT(std::from_range_t, Range&&) -> ContainerWithFromRangeT<std::ranges::range_value_t<Range>>;
+
+template <std::ranges::input_range Range>
+ContainerWithFromRangeT(std::from_range_t, Range&&, int, char) ->
+    ContainerWithFromRangeT<std::ranges::range_value_t<Range>>;
+
+template <class ElementType>
+struct ContainerWithBeginEndPair : Container<ElementType, CtrChoice::BeginEndPair> {
+  using Container<ElementType, CtrChoice::BeginEndPair>::Container;
+};
+
+template <class Iter>
+ContainerWithBeginEndPair(Iter, Iter) -> ContainerWithBeginEndPair<std::iter_value_t<Iter>>;
+
+template <class Iter>
+ContainerWithBeginEndPair(Iter, Iter, int, char) -> ContainerWithBeginEndPair<std::iter_value_t<Iter>>;
+
+constexpr bool test() {
+  std::array in = {1, 2, 3, 4, 5};
+  int arg1 = 42;
+  char arg2 = 'a';
+
+  { // Case 1 -- can construct directly from the given range.
+    {
+      std::same_as<ContainerWithDirectCtr<int>> decltype(auto) result = std::ranges::to<ContainerWithDirectCtr>(in);
+
+      assert(result.ctr_choice == CtrChoice::DirectCtr);
+      assert(std::ranges::equal(result, in));
+      assert((in | std::ranges::to<ContainerWithDirectCtr>()) == result);
+    }
+
+    { // Extra arguments.
+      std::same_as<ContainerWithDirectCtr<int>> decltype(auto) result =
+          std::ranges::to<ContainerWithDirectCtr>(in, arg1, arg2);
+
+      assert(result.ctr_choice == CtrChoice::DirectCtr);
+      assert(std::ranges::equal(result, in));
+      assert(result.extra_arg1 == arg1);
+      assert(result.extra_arg2 == arg2);
+      assert((in | std::ranges::to<ContainerWithDirectCtr>(arg1, arg2)) == result);
+    }
+  }
+
+  { // Case 2 -- can construct from the given range using the `from_range_t` tagged constructor.
+    {
+      std::same_as<ContainerWithFromRangeT<int>> decltype(auto) result = std::ranges::to<ContainerWithFromRangeT>(in);
+
+      assert(result.ctr_choice == CtrChoice::FromRangeT);
+      assert(std::ranges::equal(result, in));
+      assert((in | std::ranges::to<ContainerWithFromRangeT>()) == result);
+    }
+
+    { // Extra arguments.
+      std::same_as<ContainerWithFromRangeT<int>> decltype(auto) result =
+          std::ranges::to<ContainerWithFromRangeT>(in, arg1, arg2);
+
+      assert(result.ctr_choice == CtrChoice::FromRangeT);
+      assert(std::ranges::equal(result, in));
+      assert(result.extra_arg1 == arg1);
+      assert(result.extra_arg2 == arg2);
+      assert((in | std::ranges::to<ContainerWithFromRangeT>(arg1, arg2)) == result);
+    }
+  }
+
+  { // Case 3 -- can construct from a begin-end iterator pair.
+    {
+      std::same_as<ContainerWithBeginEndPair<int>> decltype(auto) result =
+          std::ranges::to<ContainerWithBeginEndPair>(in);
+
+      assert(result.ctr_choice == CtrChoice::BeginEndPair);
+      assert(std::ranges::equal(result, in));
+      assert((in | std::ranges::to<ContainerWithBeginEndPair>()) == result);
+    }
+
+    { // Extra arguments.
+      std::same_as<ContainerWithBeginEndPair<int>> decltype(auto) result =
+          std::ranges::to<ContainerWithBeginEndPair>(in, arg1, arg2);
+
+      assert(result.ctr_choice == CtrChoice::BeginEndPair);
+      assert(std::ranges::equal(result, in));
+      assert(result.extra_arg1 == arg1);
+      assert(result.extra_arg2 == arg2);
+      assert((in | std::ranges::to<ContainerWithBeginEndPair>(arg1, arg2)) == result);
+    }
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.utility/range.utility.conv/to_std_containers.pass.cpp b/libcxx/test/std/ranges/range.utility/range.utility.conv/to_std_containers.pass.cpp
new file mode 100644 (file)
index 0000000..4c19ea4
--- /dev/null
@@ -0,0 +1,214 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// Test that `ranges::to` can be used to convert between arbitrary standard containers.
+
+#include <ranges>
+
+#include <algorithm>
+#include <cassert>
+#include <deque>
+#include <forward_list>
+#include <list>
+#include <map>
+#include <queue>
+#include <set>
+#include <stack>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include "test_iterators.h"
+#include "test_range.h"
+#include "type_algorithms.h"
+#include "unwrap_container_adaptor.h"
+
+std::vector<std::vector<int>> ints = {
+  {5, 1, 3, 4, 2},
+  {3},
+  {}
+};
+
+std::vector<std::vector<char>> chars = {
+  {'a', 'b', 'c'},
+  {'a'},
+  {}
+};
+
+std::vector<std::vector<std::pair<const int, int>>> pairs = {
+  {{1, 2}, {3, 4}, {5, 6}, {7, 8}, {9, 0}},
+  {{1, 2}},
+  {}
+};
+
+template <class From, class To>
+void test_is_equal(std::vector<std::vector<typename From::value_type>> inputs) {
+  for (const auto& in : inputs) {
+    From from(in.begin(), in.end());
+    std::same_as<To> decltype(auto) result = std::ranges::to<To>(from);
+    assert(std::ranges::equal(in, result));
+  }
+}
+
+template <class From, class To>
+void test_is_permutation(std::vector<std::vector<typename From::value_type>> inputs) {
+  for (const auto& in : inputs) {
+    From from(in.begin(), in.end());
+    std::same_as<To> decltype(auto) result = std::ranges::to<To>(in);
+    assert(std::ranges::is_permutation(in, result));
+  }
+}
+
+template <class From, class To>
+void test_is_equal_for_adaptors(std::vector<std::vector<typename From::value_type>> inputs) {
+  for (const auto& in : inputs) {
+    From from(in.begin(), in.end());
+    std::same_as<To> decltype(auto) result = std::ranges::to<To>(in);
+
+    UnwrapAdaptor<From> unwrap_from(std::move(from));
+    UnwrapAdaptor<To> unwrap_to(std::move(result));
+    assert(std::ranges::is_permutation(unwrap_from.get_container(), unwrap_to.get_container()));
+  }
+}
+
+template <class T>
+using sequence_containers = types::type_list<
+    std::vector<T>,
+    std::deque<T>,
+    std::list<T>,
+    std::forward_list<T>
+>;
+
+template <class T>
+using associative_sets = types::type_list<
+    std::set<T>,
+    std::multiset<T>
+>;
+
+template <class K, class V>
+using associative_maps = types::type_list<
+    std::map<K, V>,
+    std::multimap<K, V>
+>;
+
+template <class T>
+using unordered_sets = types::type_list<
+    std::unordered_set<T>,
+    std::unordered_multiset<T>
+>;
+
+template <class K, class V>
+using unordered_maps = types::type_list<
+    std::unordered_map<K, V>,
+    std::unordered_multimap<K, V>
+>;
+
+template <class T>
+using container_adaptors = types::type_list<
+    std::stack<T>,
+    std::queue<T>,
+    std::priority_queue<T>
+>;
+
+template <class T>
+using sequences_and_sets = types::concatenate_t<sequence_containers<T>, associative_sets<T>, unordered_sets<T>>;
+
+template <class K, class V>
+using all_containers = types::concatenate_t<
+    sequence_containers<std::pair<const K, V>>,
+    associative_sets<std::pair<const K, V>>,
+    associative_maps<K, V>,
+    unordered_sets<std::pair<const K, V>>,
+    unordered_maps<K, V>>;
+
+// This is necessary to be able to use `pair`s with unordered sets.
+template <class K, class V>
+struct std::hash<std::pair<const K, V>> {
+  std::size_t operator()(const std::pair<const K, V>& p) const {
+    std::size_t h1 = std::hash<K>{}(p.first);
+    std::size_t h2 = std::hash<V>{}(p.second);
+    return h1 ^ (h2 << 1);
+  }
+};
+
+void test() {
+  { // Conversions always preserving equality.
+    { // sequences <-> sequences
+      types::for_each(sequence_containers<int>{}, []<class From>() {
+        types::for_each(sequence_containers<int>{}, []<class To>() {
+          test_is_equal<From, To>(ints);
+        });
+      });
+
+      types::for_each(sequence_containers<int>{}, []<class From>() {
+        types::for_each(sequence_containers<double>{}, []<class To>() {
+          test_is_equal<From, To>(ints);
+        });
+      });
+    }
+
+    { // sequences <-> string
+      types::for_each(sequence_containers<char>{}, []<class Seq>() {
+        test_is_equal<Seq, std::basic_string<char>>(chars);
+        test_is_equal<std::basic_string<char>, Seq>(chars);
+      });
+    }
+  }
+
+  { // sequences/sets <-> sequences/sets
+    types::for_each(sequences_and_sets<int>{}, []<class From>() {
+      types::for_each(sequences_and_sets<int>{}, []<class To>() {
+        test_is_permutation<From, To>(ints);
+      });
+    });
+
+    types::for_each(sequences_and_sets<int>{}, []<class From>() {
+      types::for_each(sequences_and_sets<double>{}, []<class To>() {
+        test_is_permutation<From, To>(ints);
+      });
+    });
+  }
+
+  { // sequences/sets/maps <-> sequences/sets/maps. Uses `pair` for non-map containers to allow mutual conversion with
+    // map types.
+    types::for_each(all_containers<int, int>{}, []<class From>() {
+      types::for_each(all_containers<int, int>{}, []<class To>() {
+        test_is_permutation<From, To>(pairs);
+      });
+    });
+
+    types::for_each(all_containers<int, int>{}, []<class From>() {
+      types::for_each(all_containers<long, double>{}, []<class To>() {
+        test_is_permutation<From, To>(pairs);
+      });
+    });
+  }
+
+  { // adaptors <-> adaptors
+    types::for_each(container_adaptors<int>{}, []<class From>() {
+      types::for_each(container_adaptors<int>{}, []<class To>() {
+        test_is_equal_for_adaptors<From, To>(ints);
+      });
+    });
+
+    types::for_each(container_adaptors<int>{}, []<class From>() {
+      types::for_each(container_adaptors<double>{}, []<class To>() {
+        test_is_equal_for_adaptors<From, To>(ints);
+      });
+    });
+  }
+}
+
+int main(int, char**) {
+  test();
+
+  return 0;
+}
index 90a0646..8815992 100755 (executable)
@@ -841,8 +841,8 @@ feature_test_macros = [
                 "forward_list",
                 "list",
                 "map",
-                "priority_queue",
                 "queue",
+                "ranges",
                 "set",
                 "stack",
                 "string",
@@ -850,7 +850,6 @@ feature_test_macros = [
                 "unordered_set",
                 "vector",
             ],
-            "unimplemented": True,
         },
         {
             "name": "__cpp_lib_ranges_zip",