[libcxx-commits] [libcxx] 866945c - [libc++][ranges] P2542R8: Implement `views::concat` (#120920)
via libcxx-commits
libcxx-commits at lists.llvm.org
Sat May 30 15:26:22 PDT 2026
Author: Nhat Nguyen
Date: 2026-05-30T23:26:16+01:00
New Revision: 866945c20298c1bca4af6973a31f4739dfe49e91
URL: https://github.com/llvm/llvm-project/commit/866945c20298c1bca4af6973a31f4739dfe49e91
DIFF: https://github.com/llvm/llvm-project/commit/866945c20298c1bca4af6973a31f4739dfe49e91.diff
LOG: [libc++][ranges] P2542R8: Implement `views::concat` (#120920)
Closes #105419
Closes #105348
Closes #105349
Closes #171314
Assisted-by: Chatgpt.
I use AI to help me write some tests, however. I have reviewed the code
I submit in the tests.
---------
Co-authored-by: A. Jiang <de34 at live.cn>
Co-authored-by: Hristo Hristov <hghristov.rmm at gmail.com>
Co-authored-by: Hristo Hristov <zingam at outlook.com>
Added:
libcxx/include/__ranges/concat_view.h
libcxx/test/libcxx/ranges/range.adaptors/range.concat/iterator.valueless_by_exception.pass.cpp
libcxx/test/libcxx/ranges/range.adaptors/range.concat/nodiscard.verify.cpp
libcxx/test/std/ranges/range.adaptors/range.concat/adaptor.pass.cpp
libcxx/test/std/ranges/range.adaptors/range.concat/begin.pass.cpp
libcxx/test/std/ranges/range.adaptors/range.concat/constraints.pass.cpp
libcxx/test/std/ranges/range.adaptors/range.concat/ctad.pass.cpp
libcxx/test/std/ranges/range.adaptors/range.concat/ctor.default.pass.cpp
libcxx/test/std/ranges/range.adaptors/range.concat/ctor.views.pass.cpp
libcxx/test/std/ranges/range.adaptors/range.concat/end.pass.cpp
libcxx/test/std/ranges/range.adaptors/range.concat/iterator/arithmetic.pass.cpp
libcxx/test/std/ranges/range.adaptors/range.concat/iterator/compare.pass.cpp
libcxx/test/std/ranges/range.adaptors/range.concat/iterator/ctor.default.pass.cpp
libcxx/test/std/ranges/range.adaptors/range.concat/iterator/ctor.other.pass.cpp
libcxx/test/std/ranges/range.adaptors/range.concat/iterator/decrement.pass.cpp
libcxx/test/std/ranges/range.adaptors/range.concat/iterator/deref.pass.cpp
libcxx/test/std/ranges/range.adaptors/range.concat/iterator/increment.pass.cpp
libcxx/test/std/ranges/range.adaptors/range.concat/iterator/iter_move.pass.cpp
libcxx/test/std/ranges/range.adaptors/range.concat/iterator/iter_swap.pass.cpp
libcxx/test/std/ranges/range.adaptors/range.concat/iterator/member_types.compile.pass.cpp
libcxx/test/std/ranges/range.adaptors/range.concat/iterator/subscript.pass.cpp
libcxx/test/std/ranges/range.adaptors/range.concat/size.pass.cpp
libcxx/test/std/ranges/range.adaptors/range.concat/types.h
Modified:
libcxx/docs/FeatureTestMacroTable.rst
libcxx/docs/ReleaseNotes/23.rst
libcxx/docs/Status/Cxx2cIssues.csv
libcxx/docs/Status/Cxx2cPapers.csv
libcxx/include/CMakeLists.txt
libcxx/include/module.modulemap.in
libcxx/include/ranges
libcxx/include/version
libcxx/modules/std/ranges.inc
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/version.version.compile.pass.cpp
libcxx/test/std/library/description/conventions/customization.point.object/cpo.compile.pass.cpp
libcxx/utils/generate_feature_test_macro_components.py
Removed:
################################################################################
diff --git a/libcxx/docs/FeatureTestMacroTable.rst b/libcxx/docs/FeatureTestMacroTable.rst
index ae48eaed1f46b..4eb15fa0eb131 100644
--- a/libcxx/docs/FeatureTestMacroTable.rst
+++ b/libcxx/docs/FeatureTestMacroTable.rst
@@ -506,7 +506,7 @@ Status
---------------------------------------------------------- -----------------
``__cpp_lib_philox_engine`` *unimplemented*
---------------------------------------------------------- -----------------
- ``__cpp_lib_ranges_concat`` *unimplemented*
+ ``__cpp_lib_ranges_concat`` ``202403L``
---------------------------------------------------------- -----------------
``__cpp_lib_ranges_indices`` ``202506L``
---------------------------------------------------------- -----------------
diff --git a/libcxx/docs/ReleaseNotes/23.rst b/libcxx/docs/ReleaseNotes/23.rst
index 843f25dc7708a..e94897ffaf437 100644
--- a/libcxx/docs/ReleaseNotes/23.rst
+++ b/libcxx/docs/ReleaseNotes/23.rst
@@ -48,6 +48,7 @@ Implemented Papers
- P2164R9: ``views::enumerate`` (`Github <https://llvm.org/PR105251>`__)
- P2322R6: ``ranges::fold`` (`Github <https://llvm.org/PR105208>`__)
- P4144R1: Remove ``span``'s ``initializer_list`` constructor for C++26 (`Github <https://llvm.org/PR189612>`__)
+- P2542R8: ``views::concat`` (`Github <https://llvm.org/PR105419>`__)
- P3383R3: ``mdspan.at()`` (`Github <https://llvm.org/PR175213>`__)
- P3508R0: Wording for "constexpr for specialized memory algorithms" (`Github <https://llvm.org/PR118379>`__)
diff --git a/libcxx/docs/Status/Cxx2cIssues.csv b/libcxx/docs/Status/Cxx2cIssues.csv
index e329e14f70ab3..20b0b93eff753 100644
--- a/libcxx/docs/Status/Cxx2cIssues.csv
+++ b/libcxx/docs/Status/Cxx2cIssues.csv
@@ -68,8 +68,8 @@
"`LWG4071 <https://wg21.link/LWG4071>`__","``reference_wrapper`` comparisons are not SFINAE-friendly","2024-06 (St. Louis)","|Complete|","19","`#105345 <https://github.com/llvm/llvm-project/issues/105345>`__",""
"`LWG4074 <https://wg21.link/LWG4074>`__","``compatible-joinable-ranges`` is underconstrained","2024-06 (St. Louis)","|Complete|","21","`#105346 <https://github.com/llvm/llvm-project/issues/105346>`__",""
"`LWG4076 <https://wg21.link/LWG4076>`__","``concat_view`` should be freestanding","2024-06 (St. Louis)","","","`#105347 <https://github.com/llvm/llvm-project/issues/105347>`__",""
-"`LWG4079 <https://wg21.link/LWG4079>`__","Missing Preconditions in ``concat_view::iterator``\`s conversion constructor","2024-06 (St. Louis)","","","`#105348 <https://github.com/llvm/llvm-project/issues/105348>`__",""
-"`LWG4082 <https://wg21.link/LWG4082>`__","``views::concat(r)`` is well-formed when ``r`` is an ``output_range``","2024-06 (St. Louis)","","","`#105349 <https://github.com/llvm/llvm-project/issues/105349>`__",""
+"`LWG4079 <https://wg21.link/LWG4079>`__","Missing Preconditions in ``concat_view::iterator``\`s conversion constructor","2024-06 (St. Louis)","|Complete|","23","`#105348 <https://github.com/llvm/llvm-project/issues/105348>`__",""
+"`LWG4082 <https://wg21.link/LWG4082>`__","``views::concat(r)`` is well-formed when ``r`` is an ``output_range``","2024-06 (St. Louis)","|Complete|","23","`#105349 <https://github.com/llvm/llvm-project/issues/105349>`__",""
"`LWG4083 <https://wg21.link/LWG4083>`__","``views::as_rvalue`` should reject non-input ranges","2024-06 (St. Louis)","|Complete|","22","`#105351 <https://github.com/llvm/llvm-project/issues/105351>`__",""
"`LWG4096 <https://wg21.link/LWG4096>`__","``views::iota(views::iota(0))`` should be rejected","2024-06 (St. Louis)","|Complete|","22","`#105352 <https://github.com/llvm/llvm-project/issues/105352>`__",""
"`LWG4098 <https://wg21.link/LWG4098>`__","``views::adjacent<0>`` should reject non-forward ranges","2024-06 (St. Louis)","","","`#105353 <https://github.com/llvm/llvm-project/issues/105353>`__",""
@@ -155,7 +155,7 @@
"`LWG4020 <https://wg21.link/LWG4020>`__","``extents::index-cast`` weirdness","2025-11 (Kona)","|Complete|","23","`#171311 <https://github.com/llvm/llvm-project/issues/171311>`__",""
"`LWG4136 <https://wg21.link/LWG4136>`__","Specify behavior of [linalg] Hermitian algorithms on diagonal with nonzero imaginary part","2025-11 (Kona)","","","`#171312 <https://github.com/llvm/llvm-project/issues/171312>`__",""
"`LWG4137 <https://wg21.link/LWG4137>`__","Fix *Mandates*, *Preconditions*, and *Complexity* elements of [linalg] algorithms","2025-11 (Kona)","","","`#171313 <https://github.com/llvm/llvm-project/issues/171313>`__",""
-"`LWG4166 <https://wg21.link/LWG4166>`__","``concat_view::end()`` should be more constrained in order to support noncopyable iterators","2025-11 (Kona)","","","`#171314 <https://github.com/llvm/llvm-project/issues/171314>`__",""
+"`LWG4166 <https://wg21.link/LWG4166>`__","``concat_view::end()`` should be more constrained in order to support noncopyable iterators","2025-11 (Kona)","|Complete|","23","`#171314 <https://github.com/llvm/llvm-project/issues/171314>`__",""
"`LWG4230 <https://wg21.link/LWG4230>`__","``simd<complex>::real/imag`` is overconstrained","2025-11 (Kona)","","","`#171316 <https://github.com/llvm/llvm-project/issues/171316>`__",""
"`LWG4243 <https://wg21.link/LWG4243>`__","``as_bytes``/``as_writable_bytes`` is broken with ``span<volatile T>``","2025-11 (Kona)","","","`#171317 <https://github.com/llvm/llvm-project/issues/171317>`__",""
"`LWG4251 <https://wg21.link/LWG4251>`__","Move assignment for ``indirect`` unnecessarily requires copy construction","2025-11 (Kona)","","","`#171318 <https://github.com/llvm/llvm-project/issues/171318>`__",""
diff --git a/libcxx/docs/Status/Cxx2cPapers.csv b/libcxx/docs/Status/Cxx2cPapers.csv
index 5b229c75cc8c0..2132e80251657 100644
--- a/libcxx/docs/Status/Cxx2cPapers.csv
+++ b/libcxx/docs/Status/Cxx2cPapers.csv
@@ -54,7 +54,7 @@
"`P3142R0 <https://wg21.link/P3142R0>`__","Printing Blank Lines with ``println``","2024-03 (Tokyo)","|Complete|","19","`#105415 <https://github.com/llvm/llvm-project/issues/105415>`__","Implemented as a DR against C++23. (MSVC STL and libstdc++ will do the same.)"
"`P2845R8 <https://wg21.link/P2845R8>`__","Formatting of ``std::filesystem::path``","2024-03 (Tokyo)","","","`#105416 <https://github.com/llvm/llvm-project/issues/105416>`__",""
"`P0493R5 <https://wg21.link/P0493R5>`__","Atomic minimum/maximum","2024-03 (Tokyo)","","","`#105418 <https://github.com/llvm/llvm-project/issues/105418>`__",""
-"`P2542R8 <https://wg21.link/P2542R8>`__","``views::concat``","2024-03 (Tokyo)","","","`#105419 <https://github.com/llvm/llvm-project/issues/105419>`__",""
+"`P2542R8 <https://wg21.link/P2542R8>`__","``views::concat``","2024-03 (Tokyo)","|Complete|","23","`#105419 <https://github.com/llvm/llvm-project/issues/105419>`__",""
"`P2591R5 <https://wg21.link/P2591R5>`__","Concatenation of strings and string views","2024-03 (Tokyo)","|Complete|","19","`#105420 <https://github.com/llvm/llvm-project/issues/105420>`__",""
"`P2248R8 <https://wg21.link/P2248R8>`__","Enabling list-initialization for algorithms","2024-03 (Tokyo)","","","`#105421 <https://github.com/llvm/llvm-project/issues/105421>`__",""
"`P2810R4 <https://wg21.link/P2810R4>`__","``is_debugger_present`` ``is_replaceable``","2024-03 (Tokyo)","","","`#105422 <https://github.com/llvm/llvm-project/issues/105422>`__",""
diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 5e9040a62dd53..10dfb4b4d58cb 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -724,6 +724,7 @@ set(files
__ranges/as_rvalue_view.h
__ranges/chunk_by_view.h
__ranges/common_view.h
+ __ranges/concat_view.h
__ranges/concepts.h
__ranges/container_compatible_range.h
__ranges/counted.h
diff --git a/libcxx/include/__ranges/concat_view.h b/libcxx/include/__ranges/concat_view.h
new file mode 100644
index 0000000000000..3bbe9db12e0f8
--- /dev/null
+++ b/libcxx/include/__ranges/concat_view.h
@@ -0,0 +1,651 @@
+// -*- 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_CONCAT_VIEW_H
+#define _LIBCPP___RANGES_CONCAT_VIEW_H
+
+#include <__assert>
+#include <__concepts/common_reference_with.h>
+#include <__concepts/constructible.h>
+#include <__concepts/convertible_to.h>
+#include <__concepts/copyable.h>
+#include <__concepts/derived_from.h>
+#include <__concepts/equality_comparable.h>
+#include <__concepts/swappable.h>
+#include <__config>
+#include <__iterator/concepts.h>
+#include <__iterator/default_sentinel.h>
+#include <__iterator/distance.h>
+#include <__iterator/incrementable_traits.h>
+#include <__iterator/iter_move.h>
+#include <__iterator/iter_swap.h>
+#include <__iterator/iterator_traits.h>
+#include <__iterator/next.h>
+#include <__ranges/access.h>
+#include <__ranges/all.h>
+#include <__ranges/concepts.h>
+#include <__ranges/movable_box.h>
+#include <__ranges/range_adaptor.h>
+#include <__ranges/size.h>
+#include <__ranges/view_interface.h>
+#include <__tuple/tuple_transform.h>
+#include <__type_traits/conditional.h>
+#include <__type_traits/decay.h>
+#include <__type_traits/is_nothrow_constructible.h>
+#include <__type_traits/make_unsigned.h>
+#include <__type_traits/maybe_const.h>
+#include <__utility/forward.h>
+#include <__utility/in_place.h>
+#include <__utility/move.h>
+#include <tuple>
+#include <variant>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+# pragma GCC system_header
+#endif
+
+_LIBCPP_PUSH_MACROS
+#include <__undef_macros>
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+#if _LIBCPP_STD_VER >= 26
+
+namespace ranges {
+
+# ifdef __cpp_pack_indexing
+template <class... _Tp>
+using __extract_last _LIBCPP_NODEBUG = _Tp...[sizeof...(_Tp) - 1];
+# else
+template <class _Tp, class... _Tail>
+struct __extract_last_impl : __extract_last_impl<_Tail...> {};
+template <class _Tp>
+struct __extract_last_impl<_Tp> {
+ using type _LIBCPP_NODEBUG = _Tp;
+};
+
+template <class... _Tp>
+using __extract_last _LIBCPP_NODEBUG = __extract_last_impl<_Tp...>::type;
+# endif
+
+template <bool _Const, class... _Tp>
+struct __all_but_first_model_sized_range;
+
+template <bool _Const, class _Head, class... _Tail>
+struct __all_but_first_model_sized_range<_Const, _Head, _Tail...> {
+ static constexpr bool value = (sized_range<__maybe_const<_Const, _Tail>> && ...);
+};
+
+template <bool _Const, class... _Views>
+concept __all_random_access = (random_access_range<__maybe_const<_Const, _Views>> && ...);
+
+template <bool _Const, class... _Views>
+concept __all_bidirectional = (bidirectional_range<__maybe_const<_Const, _Views>> && ...);
+
+template <bool _Const, class... _Views>
+concept __all_forward = (forward_range<__maybe_const<_Const, _Views>> && ...);
+
+template <bool _Const, class _First, class... _Tail>
+struct __all_common_ignore_last {
+ static constexpr bool value =
+ common_range<__maybe_const<_Const, _First>> && __all_common_ignore_last<_Const, _Tail...>::value;
+};
+
+template <bool _Const, class _Tail>
+struct __all_common_ignore_last<_Const, _Tail> {
+ static constexpr bool value = true;
+};
+
+template <bool _Const, class... _Rs>
+concept __concat_is_random_access =
+ (__all_random_access<_Const, _Rs...>) && (__all_common_ignore_last<_Const, _Rs...>::value);
+
+template <bool _Const, class... _Rs>
+concept __concat_is_bidirectional =
+ (__all_bidirectional<_Const, _Rs...>) && (__all_common_ignore_last<_Const, _Rs...>::value);
+
+template <input_range... _Views>
+ requires((view<_Views> && ...) && (sizeof...(_Views) > 0) && __concatable<_Views...>)
+class concat_view : public view_interface<concat_view<_Views...>> {
+ tuple<_Views...> __views_;
+
+ template <bool _Const>
+ class __iterator;
+
+public:
+ _LIBCPP_HIDE_FROM_ABI constexpr concat_view() = default;
+
+ _LIBCPP_HIDE_FROM_ABI constexpr explicit concat_view(_Views... __views) : __views_(std::move(__views)...) {}
+
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr __iterator<false> begin()
+ requires(!(__simple_view<_Views> && ...))
+ {
+ __iterator<false> __it(this, in_place_index<0>, ranges::begin(std::get<0>(__views_)));
+ __it.template __satisfy<0>();
+ return __it;
+ }
+
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr __iterator<true> begin() const
+ requires((range<const _Views> && ...) && __concatable<const _Views...>)
+ {
+ __iterator<true> __it(this, in_place_index<0>, ranges::begin(std::get<0>(__views_)));
+ __it.template __satisfy<0>();
+ return __it;
+ }
+
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto end()
+ requires(!(__simple_view<_Views> && ...))
+ {
+ if constexpr (__all_forward<false, _Views...> && common_range<__maybe_const<false, __extract_last<_Views...>>>) {
+ constexpr auto __n = sizeof...(_Views);
+ return __iterator<false>(this, in_place_index<__n - 1>, ranges::end(std::get<__n - 1>(__views_)));
+ } else {
+ return default_sentinel;
+ }
+ }
+
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto end() const
+ requires((range<const _Views> && ...) && __concatable<const _Views...>)
+ {
+ if constexpr (__all_forward<true, _Views...> && common_range<__maybe_const<true, __extract_last<_Views...>>>) {
+ constexpr auto __n = sizeof...(_Views);
+ return __iterator<true>(this, in_place_index<__n - 1>, ranges::end(std::get<__n - 1>(__views_)));
+ } else {
+ return default_sentinel;
+ }
+ }
+
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto size()
+ requires(sized_range<_Views> && ...)
+ {
+ return std::apply(
+ [](auto... __sizes) { return (make_unsigned_t<common_type_t<decltype(__sizes)...>>(__sizes) + ...); },
+ std::__tuple_transform(ranges::size, __views_));
+ }
+
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto size() const
+ requires(sized_range<const _Views> && ...)
+ {
+ return std::apply(
+ [](auto... __sizes) { return (make_unsigned_t<common_type_t<decltype(__sizes)...>>(__sizes) + ...); },
+ std::__tuple_transform(ranges::size, __views_));
+ }
+};
+
+template <class... _Views>
+concat_view(_Views&&...) -> concat_view<views::all_t<_Views>...>;
+
+template <bool _Const, typename... _Views>
+struct __concat_view_iterator_category {};
+
+template <bool _Const, typename... _Views>
+ requires __all_forward<_Const, _Views...>
+struct __concat_view_iterator_category<_Const, _Views...> {
+private:
+ constexpr static bool __derive_pack_random_iterator =
+ (derived_from<typename iterator_traits<iterator_t<__maybe_const<_Const, _Views>>>::iterator_category,
+ random_access_iterator_tag> &&
+ ...);
+ constexpr static bool __derive_pack_bidirectional_iterator =
+ (derived_from<typename iterator_traits<iterator_t<__maybe_const<_Const, _Views>>>::iterator_category,
+ bidirectional_iterator_tag> &&
+ ...);
+ constexpr static bool __derive_pack_forward_iterator =
+ (derived_from<typename iterator_traits< iterator_t<__maybe_const<_Const, _Views>>>::iterator_category,
+ forward_iterator_tag> &&
+ ...);
+
+public:
+ using iterator_category =
+ _If<!is_reference_v<__concat_reference_t<__maybe_const<_Const, _Views>...>>,
+ input_iterator_tag,
+ _If<__derive_pack_random_iterator,
+ random_access_iterator_tag,
+ _If<__derive_pack_bidirectional_iterator,
+ bidirectional_iterator_tag,
+ _If<__derive_pack_forward_iterator, forward_iterator_tag, input_iterator_tag > > > >;
+};
+
+template <input_range... _Views>
+ requires((view<_Views> && ...) && (sizeof...(_Views) > 0) && __concatable<_Views...>)
+template <bool _Const>
+class concat_view<_Views...>::__iterator : public __concat_view_iterator_category<_Const, _Views...> {
+public:
+ using iterator_concept =
+ _If<__concat_is_random_access<_Const, _Views...>,
+ random_access_iterator_tag,
+ _If<__concat_is_bidirectional<_Const, _Views...>,
+ bidirectional_iterator_tag,
+ _If< __all_forward<_Const, _Views...>, forward_iterator_tag, input_iterator_tag > > >;
+ using value_type = __concat_value_t<__maybe_const<_Const, _Views>...>;
+ using
diff erence_type = common_type_t<range_
diff erence_t<__maybe_const<_Const, _Views>>...>;
+
+private:
+ using __base_iter _LIBCPP_NODEBUG = variant<iterator_t<__maybe_const<_Const, _Views>>...>;
+ __base_iter __it_;
+ __maybe_const<_Const, concat_view>* __parent_ = nullptr;
+
+ template <size_t _Idx>
+ _LIBCPP_HIDE_FROM_ABI constexpr void __satisfy() {
+ if constexpr (_Idx < (sizeof...(_Views) - 1)) {
+ if (std::get<_Idx>(__it_) == ranges::end(std::get<_Idx>(__parent_->__views_))) {
+ __it_.template emplace<_Idx + 1>(ranges::begin(std::get<_Idx + 1>(__parent_->__views_)));
+ __satisfy<_Idx + 1>();
+ }
+ }
+ }
+
+ template <size_t _Idx>
+ _LIBCPP_HIDE_FROM_ABI constexpr void __prev() {
+ if constexpr (_Idx == 0) {
+ --std::get<0>(__it_);
+ } else {
+ if (std::get<_Idx>(__it_) == ranges::begin(std::get<_Idx>(__parent_->__views_))) {
+ __it_.template emplace<_Idx - 1>(ranges::end(std::get<_Idx - 1>(__parent_->__views_)));
+ __prev<_Idx - 1>();
+ } else {
+ --std::get<_Idx>(__it_);
+ }
+ }
+ }
+
+ template <size_t _Idx>
+ _LIBCPP_HIDE_FROM_ABI constexpr void __advance_fwd(
diff erence_type __offset,
diff erence_type __steps) {
+ using __underlying_
diff _type = iter_
diff erence_t<variant_alternative_t<_Idx, __base_iter>>;
+ if constexpr (_Idx == sizeof...(_Views) - 1) {
+ std::get<_Idx>(__it_) += static_cast<__underlying_
diff _type>(__steps);
+ } else {
+ auto __n_size = ranges::distance(std::get<_Idx>(__parent_->__views_));
+ if (__offset + __steps < __n_size) {
+ std::get<_Idx>(__it_) += static_cast<__underlying_
diff _type>(__steps);
+ } else {
+ __it_.template emplace<_Idx + 1>(ranges::begin(std::get<_Idx + 1>(__parent_->__views_)));
+ __advance_fwd<_Idx + 1>(0, __offset + __steps - __n_size);
+ }
+ }
+ }
+
+ template <size_t _Idx>
+ _LIBCPP_HIDE_FROM_ABI constexpr void __advance_bwd(
diff erence_type __offset,
diff erence_type __steps) {
+ using __underlying_
diff _type = iter_
diff erence_t<variant_alternative_t<_Idx, __base_iter>>;
+ if constexpr (_Idx == 0) {
+ std::get<_Idx>(__it_) -= static_cast<__underlying_
diff _type>(__steps);
+ } else {
+ if (__offset >= __steps) {
+ std::get<_Idx>(__it_) -= static_cast<__underlying_
diff _type>(__steps);
+ } else {
+ auto __prev_size = ranges::distance(std::get<_Idx - 1>(__parent_->__views_));
+ __it_.template emplace<_Idx - 1>(ranges::end(std::get<_Idx - 1>(__parent_->__views_)));
+ __advance_bwd<_Idx - 1>(__prev_size, __steps - __offset);
+ }
+ }
+ }
+
+ template <typename _Func>
+ _LIBCPP_HIDE_FROM_ABI constexpr auto __invoke_at_index(_Func&& __func) const {
+ // TODO(GCC 16): Just capture `this` when GCC PR113563 and PR121008 are fixed.
+ return [&__func, &__view_iter = *this]<std::size_t _Is>(this auto&& __self) {
+ if (_Is == __view_iter.__it_.index()) {
+ return __func.template operator()<_Is>();
+ }
+ if constexpr (_Is + 1 < sizeof...(_Views)) {
+ return __self.template operator()<_Is + 1>();
+ }
+ __builtin_unreachable();
+ }.template operator()<0>();
+ }
+
+ template <size_t... _Is, typename _Func>
+ _LIBCPP_HIDE_FROM_ABI constexpr void __apply_at_index(size_t __index, _Func&& __func, index_sequence<_Is...>) const {
+ ((__index == _Is ? (static_cast<void>(__func(integral_constant<size_t, _Is>{})), 0) : 0), ...);
+ }
+
+ template <size_t _Idx, typename _Func>
+ _LIBCPP_HIDE_FROM_ABI constexpr void __apply_at_index(size_t __index, _Func&& __func) const {
+ __apply_at_index(__index, std::forward<_Func>(__func), make_index_sequence<_Idx>{});
+ }
+
+ template <class... _Args>
+ _LIBCPP_HIDE_FROM_ABI explicit constexpr __iterator(__maybe_const<_Const, concat_view>* __parent, _Args&&... __args)
+ requires constructible_from<__base_iter, _Args&&...>
+ : __it_(std::forward<_Args>(__args)...), __parent_(__parent) {}
+
+ friend class concat_view;
+ friend class __iterator<!_Const>;
+
+public:
+ _LIBCPP_HIDE_FROM_ABI __iterator() = default;
+
+ _LIBCPP_HIDE_FROM_ABI constexpr __iterator(__iterator<!_Const> __i)
+ requires _Const && (convertible_to<iterator_t<_Views>, iterator_t<const _Views>> && ...)
+ : __it_([&__src = __i.__it_]<size_t... _Indices>(size_t __idx, index_sequence<_Indices...>) -> __base_iter {
+ _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+ !__src.valueless_by_exception(), "Trying to convert from a valueless iterator of concat_view.");
+ using __src_lref = decltype((__src));
+ using __construction_fptr = __base_iter (*)(__src_lref);
+ static constexpr __construction_fptr __vtable[]{[](__src_lref __src_var) -> __base_iter {
+ return __base_iter(in_place_index<_Indices>, std::__unchecked_get<_Indices>(std::move(__src_var)));
+ }...};
+ return __vtable[__idx](__src);
+ }(__i.__it_.index(), make_index_sequence<variant_size_v<__base_iter>>{})),
+ __parent_(__i.__parent_) {}
+
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr decltype(auto) operator*() const {
+ _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+ !__it_.valueless_by_exception(), "Trying to dereference a valueless iterator of concat_view.");
+ return __variant_detail::__visitation::__variant::__visit_value(
+ [](auto&& __it) -> __concat_reference_t<__maybe_const<_Const, _Views>...> { return *__it; }, __it_);
+ }
+
+ _LIBCPP_HIDE_FROM_ABI constexpr __iterator& operator++() {
+ _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+ !__it_.valueless_by_exception(), "Trying to increment a valueless iterator of concat_view.");
+ size_t __active_index = __it_.index();
+ __apply_at_index<variant_size_v<decltype(__it_)>>(__active_index, [&](auto __index_constant) {
+ constexpr size_t __i = __index_constant.value;
+ ++std::__unchecked_get<__i>(__it_);
+ __satisfy<__i>();
+ });
+ return *this;
+ }
+
+ _LIBCPP_HIDE_FROM_ABI constexpr void operator++(int) { ++*this; }
+
+ _LIBCPP_HIDE_FROM_ABI constexpr __iterator operator++(int)
+ requires(__all_forward<_Const, _Views...>)
+ {
+ auto __tmp = *this;
+ ++*this;
+ return __tmp;
+ }
+
+ _LIBCPP_HIDE_FROM_ABI constexpr __iterator& operator--()
+ requires __concat_is_bidirectional<_Const, _Views...>
+ {
+ _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+ !__it_.valueless_by_exception(), "Trying to decrement a valueless iterator of concat_view.");
+ size_t __active_index = __it_.index();
+ __apply_at_index<variant_size_v<decltype(__it_)>>(__active_index, [&](auto __index_constant) {
+ constexpr size_t __i = __index_constant.value;
+ __prev<__i>();
+ });
+ return *this;
+ }
+
+ _LIBCPP_HIDE_FROM_ABI constexpr __iterator operator--(int)
+ requires __concat_is_bidirectional<_Const, _Views...>
+ {
+ auto __tmp = *this;
+ --*this;
+ return __tmp;
+ }
+
+ _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const __iterator& __x, const __iterator& __y)
+ requires(equality_comparable<iterator_t<__maybe_const<_Const, _Views>>> && ...)
+ {
+ _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(!__x.__it_.valueless_by_exception() && !__y.__it_.valueless_by_exception(),
+ "Trying to compare a valueless iterator of concat_view.");
+ return __x.__it_ == __y.__it_;
+ }
+
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr decltype(auto) operator[](
diff erence_type __n) const
+ requires __concat_is_random_access<_Const, _Views...>
+ {
+ return *((*this) + __n);
+ }
+
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI friend constexpr __iterator operator+(const __iterator& __it,
diff erence_type __n)
+ requires __concat_is_random_access<_Const, _Views...>
+ {
+ auto __temp = __it;
+ __temp += __n;
+ return __temp;
+ }
+
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI friend constexpr __iterator operator+(
diff erence_type __n, const __iterator& __it)
+ requires __concat_is_random_access<_Const, _Views...>
+ {
+ return __it + __n;
+ }
+
+ _LIBCPP_HIDE_FROM_ABI constexpr __iterator& operator+=(
diff erence_type __n)
+ requires __concat_is_random_access<_Const, _Views...>
+ {
+ _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+ !__it_.valueless_by_exception(), "Trying to increment a valueless iterator of concat_view.");
+ size_t __active_index = __it_.index();
+ if (__n > 0) {
+ __apply_at_index<tuple_size_v<decltype(__parent_->__views_)>>(__active_index, [&](auto __index_constant) {
+ constexpr size_t __i = __index_constant.value;
+ auto& __active_view = std::get<__i>(__parent_->__views_);
+
diff erence_type __idx = std::get<__i>(__it_) - ranges::begin(__active_view);
+ __advance_fwd<__i>(__idx, __n);
+ });
+
+ }
+
+ else if (__n < 0) {
+ __apply_at_index<tuple_size_v<decltype(__parent_->__views_)>>(__active_index, [&](auto __index_constant) {
+ constexpr size_t __i = __index_constant.value;
+ auto& __active_view = std::get<__i>(__parent_->__views_);
+
diff erence_type __idx = std::get<__i>(__it_) - ranges::begin(__active_view);
+ __advance_bwd<__i>(__idx, -__n);
+ });
+ }
+
+ return *this;
+ }
+
+ _LIBCPP_HIDE_FROM_ABI constexpr __iterator& operator-=(
diff erence_type __n)
+ requires __concat_is_random_access<_Const, _Views...>
+ {
+ *this += -__n;
+ return *this;
+ }
+
+ _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const __iterator& __it, default_sentinel_t) {
+ _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+ !__it.__it_.valueless_by_exception(),
+ "Trying to compare a valueless iterator of concat_view with the default sentinel.");
+ constexpr auto __last_idx = sizeof...(_Views) - 1;
+ return __it.__it_.index() == __last_idx &&
+ std::__unchecked_get<__last_idx>(__it.__it_) == ranges::end(std::get<__last_idx>(__it.__parent_->__views_));
+ }
+
+ _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator<(const __iterator& __x, const __iterator& __y)
+ requires(__all_random_access<_Const, _Views> && ...)
+ {
+ _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(!__x.__it_.valueless_by_exception() && !__y.__it_.valueless_by_exception(),
+ "Trying to compare a valueless iterator of concat_view.");
+ return __x.__it_ < __y.__it_;
+ }
+
+ _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator>(const __iterator& __x, const __iterator& __y)
+ requires(__all_random_access<_Const, _Views> && ...)
+ {
+ _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(!__x.__it_.valueless_by_exception() && !__y.__it_.valueless_by_exception(),
+ "Trying to compare a valueless iterator of concat_view.");
+ return __x.__it_ > __y.__it_;
+ }
+
+ _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator<=(const __iterator& __x, const __iterator& __y)
+ requires(__all_random_access<_Const, _Views> && ...)
+ {
+ _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(!__x.__it_.valueless_by_exception() && !__y.__it_.valueless_by_exception(),
+ "Trying to compare a valueless iterator of concat_view.");
+ return __x.__it_ <= __y.__it_;
+ }
+
+ _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator>=(const __iterator& __x, const __iterator& __y)
+ requires(__all_random_access<_Const, _Views> && ...)
+ {
+ _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(!__x.__it_.valueless_by_exception() && !__y.__it_.valueless_by_exception(),
+ "Trying to compare a valueless iterator of concat_view.");
+ return __x.__it_ >= __y.__it_;
+ }
+
+ _LIBCPP_HIDE_FROM_ABI friend constexpr auto operator<=>(const __iterator& __x, const __iterator& __y)
+ requires((__all_random_access<_Const, _Views> && ...) &&
+ (three_way_comparable<iterator_t<__maybe_const<_Const, _Views>>> && ...))
+ {
+ _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(!__x.__it_.valueless_by_exception() && !__y.__it_.valueless_by_exception(),
+ "Trying to compare a valueless iterator of concat_view.");
+ return __x.__it_ <=> __y.__it_;
+ }
+
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI friend constexpr decltype(auto) iter_move(const __iterator& __it) noexcept(
+
+ ((is_nothrow_invocable_v< decltype(ranges::iter_move), const iterator_t<__maybe_const<_Const, _Views>>& > &&
+ is_nothrow_convertible_v< range_rvalue_reference_t<__maybe_const<_Const, _Views>>,
+ __concat_rvalue_reference_t<__maybe_const<_Const, _Views>...> >) &&
+ ...))
+
+ {
+ _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+ !__it.__it_.valueless_by_exception(), "Trying to apply iter_move to a valueless iterator of concat_view.");
+ return __variant_detail::__visitation::__variant::__visit_value(
+ [](const auto& __i) -> __concat_rvalue_reference_t<__maybe_const<_Const, _Views>...> {
+ return ranges::iter_move(__i);
+ },
+ __it.__it_);
+ }
+
+ _LIBCPP_HIDE_FROM_ABI friend constexpr void iter_swap(const __iterator& __x, const __iterator& __y)
+
+ noexcept((noexcept(ranges::swap(*__x, *__y))) &&
+ (noexcept(ranges::iter_swap(std::declval<const iterator_t<__maybe_const<_Const, _Views>>>(),
+ std::declval<const iterator_t<__maybe_const<_Const, _Views>>>())) &&
+ ...))
+
+ requires swappable_with<iter_reference_t<__iterator>, iter_reference_t<__iterator>> &&
+ (... && indirectly_swappable<iterator_t<__maybe_const<_Const, _Views>>>)
+ {
+ _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+ !__x.__it_.valueless_by_exception() && !__y.__it_.valueless_by_exception(),
+ "Trying to swap iterators of concat_view where at least one iterator is valueless.");
+ __variant_detail::__visitation::__variant::__visit_value(
+ [&](const auto& __it1, const auto& __it2) {
+ if constexpr (is_same_v<decltype(__it1), decltype(__it2)>) {
+ ranges::iter_swap(__it1, __it2);
+ } else {
+ ranges::swap(*__x, *__y);
+ }
+ },
+ __x.__it_,
+ __y.__it_);
+ }
+
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI friend constexpr
diff erence_type
+ operator-(const __iterator& __x, const __iterator& __y)
+ requires __concat_is_random_access<_Const, _Views...>
+ {
+ _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+ !__x.__it_.valueless_by_exception() && !__y.__it_.valueless_by_exception(),
+ "Trying to subtract two iterators of concat_view where at least one iterator is valueless.");
+ return __x.__invoke_at_index([&]<std::size_t __index_x>() ->
diff erence_type {
+ return __y.__invoke_at_index([&]<std::size_t __index_y>() ->
diff erence_type {
+ if constexpr (__index_x > __index_y) {
+ auto __dx = ranges::distance(
+ ranges::begin(std::get<__index_x>(__x.__parent_->__views_)), std::get<__index_x>(__x.__it_));
+ auto __dy = ranges::distance(
+ std::get<__index_y>(__y.__it_), ranges::end(std::get<__index_y>(__y.__parent_->__views_)));
+
diff erence_type __s = [&]<std::size_t __start, std::size_t __end>(this auto&& __self) ->
diff erence_type {
+ if constexpr (__start < __end) {
+ return ranges::size(std::get<__start>(__x.__parent_->__views_)) +
+ __self.template operator()<__start + 1, __end>();
+ }
+ return 0;
+ }.template operator()<__index_y + 1, __index_x>();
+ return __dy + __s + __dx;
+ } else if constexpr (__index_x < __index_y) {
+ return -(__y - __x);
+ } else {
+ return std::get<__index_x>(__x.__it_) - std::get<__index_y>(__y.__it_);
+ }
+ });
+ });
+ }
+
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI friend constexpr __iterator operator-(const __iterator& __it,
diff erence_type __n)
+ requires __concat_is_random_access<_Const, _Views...>
+ {
+ _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+ !__it.__it_.valueless_by_exception(), "Trying to subtract a valuess iterators of concat_view.");
+ auto __temp = __it;
+ __temp -= __n;
+ return __temp;
+ }
+
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI friend constexpr
diff erence_type
+ operator-(const __iterator& __x, default_sentinel_t)
+ requires(sized_sentinel_for<sentinel_t<__maybe_const<_Const, _Views>>, iterator_t<__maybe_const<_Const, _Views>>> &&
+ ...) &&
+ (__all_but_first_model_sized_range<_Const, _Views...>::value)
+ {
+ _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+ !__x.__it_.valueless_by_exception(),
+ "Trying to subtract a valuess iterators of concat_view from the default sentinel.");
+ return __x.__invoke_at_index([&]<std::size_t __index_x>() ->
diff erence_type {
+ auto __dx =
+ ranges::distance(std::get<__index_x>(__x.__it_), ranges::end(std::get<__index_x>(__x.__parent_->__views_)));
+
diff erence_type __s = [&]<std::size_t __start, std::size_t __end>(this auto&& __self) ->
diff erence_type {
+ if constexpr (__start < __end) {
+ return ranges::size(std::get<__start>(__x.__parent_->__views_)) +
+ __self.template operator()<__start + 1, __end>();
+ }
+ return 0;
+ }.template operator()<__index_x + 1, sizeof...(_Views)>();
+ return -(__dx + __s);
+ });
+ }
+
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI friend constexpr
diff erence_type
+ operator-(default_sentinel_t, const __iterator& __x)
+ requires(sized_sentinel_for<sentinel_t<__maybe_const<_Const, _Views>>, iterator_t<__maybe_const<_Const, _Views>>> &&
+ ...) &&
+ (__all_but_first_model_sized_range<_Const, _Views...>::value)
+ {
+ return -(__x - default_sentinel);
+ }
+};
+
+namespace views {
+namespace __concat {
+struct __fn {
+ template <input_range _Range>
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI static constexpr auto
+ operator()(_Range&& __range) noexcept(noexcept(views::all((std::forward<_Range>(__range)))))
+ -> decltype(views::all((std::forward<_Range>(__range)))) {
+ return views::all(std::forward<_Range>(__range));
+ }
+
+ template <class _FirstRange, class... _TailRanges>
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI static constexpr auto
+ operator()(_FirstRange&& __first, _TailRanges&&... __tail) noexcept(
+ noexcept(concat_view(std::forward<_FirstRange>(__first), std::forward<_TailRanges>(__tail)...)))
+ -> decltype(concat_view(std::forward<_FirstRange>(__first), std::forward<_TailRanges>(__tail)...)) {
+ return concat_view(std::forward<_FirstRange>(__first), std::forward<_TailRanges>(__tail)...);
+ }
+};
+} // namespace __concat
+
+inline namespace __cpo {
+inline constexpr auto concat = __concat::__fn{};
+} // namespace __cpo
+} // namespace views
+
+} // namespace ranges
+
+#endif // _LIBCPP_STD_VER >= 26
+
+_LIBCPP_END_NAMESPACE_STD
+
+_LIBCPP_POP_MACROS
+
+#endif // _LIBCPP___RANGES_CONCAT_VIEW_H
diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in
index 51a2b77a9ba45..2ccb455b7408d 100644
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -1886,6 +1886,7 @@ module std [system] {
export std.functional.bind_back
}
module common_view { header "__ranges/common_view.h" }
+ module concat_view { header "__ranges/concat_view.h" }
module concepts { header "__ranges/concepts.h" }
module container_compatible_range { header "__ranges/container_compatible_range.h" }
module counted {
diff --git a/libcxx/include/ranges b/libcxx/include/ranges
index 308224427db60..de05c31744fcf 100644
--- a/libcxx/include/ranges
+++ b/libcxx/include/ranges
@@ -201,6 +201,16 @@ namespace std::ranges {
inline constexpr unspecified filter = unspecified;
}
+ // [range.concat], concat view
+ template <input_range... Views>
+ requires (view<Views> && ...) && (sizeof...(Views) > 0) &&
+ concatable<Views...>
+ class concat_view;
+
+ namespace views {
+ inline constexpr unspecified concat = unspecified;
+ }
+
// [range.drop], drop view
template<view V>
class drop_view;
@@ -514,6 +524,10 @@ namespace std {
# include <__ranges/zip_view.h>
# endif
+# if _LIBCPP_STD_VER >= 26
+# include <__ranges/concat_view.h>
+# endif
+
# include <version>
// standard-mandated includes
diff --git a/libcxx/include/version b/libcxx/include/version
index 1c683b67e5700..471eb3c104b53 100644
--- a/libcxx/include/version
+++ b/libcxx/include/version
@@ -614,7 +614,7 @@ __cpp_lib_void_t 201411L <type_traits>
# undef __cpp_lib_out_ptr
# define __cpp_lib_out_ptr 202311L
// # define __cpp_lib_philox_engine 202406L
-// # define __cpp_lib_ranges_concat 202403L
+# define __cpp_lib_ranges_concat 202403L
# define __cpp_lib_ranges_indices 202506L
# define __cpp_lib_ratio 202306L
// # define __cpp_lib_rcu 202306L
diff --git a/libcxx/modules/std/ranges.inc b/libcxx/modules/std/ranges.inc
index 576f0650438e3..876942bdffbf5 100644
--- a/libcxx/modules/std/ranges.inc
+++ b/libcxx/modules/std/ranges.inc
@@ -181,6 +181,15 @@ export namespace std {
} // namespace views
#endif // _LIBCPP_STD_VER >= 23
+#if _LIBCPP_STD_VER >= 26
+ // [range.concat.view], concat_view
+ using std::ranges::concat_view;
+
+ namespace views {
+ using std::ranges::views::concat;
+ } // namespace views
+#endif // _LIBCPP_STD_VER >= 26
+
// [range.filter], filter view
using std::ranges::filter_view;
diff --git a/libcxx/test/libcxx/ranges/range.adaptors/range.concat/iterator.valueless_by_exception.pass.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.concat/iterator.valueless_by_exception.pass.cpp
new file mode 100644
index 0000000000000..8e0ff79e16a27
--- /dev/null
+++ b/libcxx/test/libcxx/ranges/range.adaptors/range.concat/iterator.valueless_by_exception.pass.cpp
@@ -0,0 +1,659 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: has-unix-headers, libcpp-hardening-mode={{extensive|debug}}
+// REQUIRES: std-at-least-c++26
+// UNSUPPORTED: libcpp-hardening-mode=none
+
+#include <iostream>
+#include <ranges>
+#include <utility>
+#include <vector>
+
+#include "check_assertion.h"
+#include "double_move_tracker.h"
+#include "test_iterators.h"
+
+int val[] = {1, 2, 3};
+
+bool flag = false;
+
+template <std::size_t N>
+struct Iter;
+
+template <std::size_t N>
+struct Iter {
+ using value_type = int;
+ using
diff erence_type = std::ptr
diff _t;
+ using reference = int&;
+ using pointer = int*;
+ using iterator_category = std::random_access_iterator_tag;
+ using iterator_concept = std::random_access_iterator_tag;
+
+private:
+ int* ptr_ = nullptr;
+
+ template <std::size_t M>
+ friend struct Iter;
+
+public:
+ Iter() = default;
+ Iter(int* ptr) : ptr_(ptr) {}
+ Iter(const Iter&) = default;
+ Iter(Iter&& other) : ptr_(other.ptr_) {
+ if (flag)
+ throw 5;
+ }
+
+ Iter& operator=(const Iter&) = default;
+ Iter& operator=(Iter&& o) {
+ ptr_ = o.ptr_;
+ if (flag)
+ throw 5;
+ return *this;
+ }
+
+ reference operator*() const { return *ptr_; }
+ pointer operator->() const { return ptr_; }
+ reference operator[](
diff erence_type n) const { return ptr_[n]; }
+
+ Iter& operator++() {
+ ++ptr_;
+ return *this;
+ }
+ Iter operator++(int) {
+ auto tmp = *this;
+ ++*this;
+ return tmp;
+ }
+ Iter& operator--() {
+ --ptr_;
+ return *this;
+ }
+ Iter operator--(int) {
+ auto tmp = *this;
+ --*this;
+ return tmp;
+ }
+
+ Iter& operator+=(
diff erence_type n) {
+ ptr_ += n;
+ return *this;
+ }
+ Iter& operator-=(
diff erence_type n) {
+ ptr_ -= n;
+ return *this;
+ }
+
+ template <std::size_t X>
+ friend Iter<X> operator+(Iter<X> it,
diff erence_type n);
+
+ template <std::size_t X>
+ friend Iter<X> operator+(
diff erence_type n, Iter<X> it);
+
+ template <std::size_t X>
+ friend Iter<X> operator-(Iter<X> it,
diff erence_type n);
+
+ template <std::size_t X, std::size_t Y>
+ friend
diff erence_type operator-(Iter<X> a, Iter<Y> b);
+
+ friend bool operator==(Iter a, Iter b) { return a.ptr_ == b.ptr_; };
+ friend bool operator<(Iter a, Iter b) { return a.ptr_ < b.ptr_; }
+ friend bool operator>(Iter a, Iter b) { return a.ptr_ > b.ptr_; }
+ friend bool operator<=(Iter a, Iter b) { return a.ptr_ <= b.ptr_; }
+ friend bool operator>=(Iter a, Iter b) { return a.ptr_ >= b.ptr_; }
+ friend auto operator<=>(Iter a, Iter b) { return a.ptr_ <=> b.ptr_; }
+};
+
+template <std::size_t X>
+inline Iter<X> operator+(Iter<X> it, std::ptr
diff _t n) {
+ return Iter<X>(it.ptr_ + n);
+}
+
+template <std::size_t X>
+inline Iter<X> operator+(std::ptr
diff _t n, Iter<X> it) {
+ return Iter<X>(it.ptr_ + n);
+}
+
+template <std::size_t X>
+inline Iter<X> operator-(Iter<X> it, std::ptr
diff _t n) {
+ return Iter<X>(it.ptr_ - n);
+}
+
+template <std::size_t X, std::size_t Y>
+inline std::ptr
diff _t operator-(Iter<X> a, Iter<Y> b) {
+ return a.ptr_ - b.ptr_;
+}
+
+template <std::size_t N>
+struct Range : std::ranges::view_base {
+ using iterator = Iter<N>;
+ using const_iterator = Iter<N>;
+ using sentinel = sentinel_wrapper<iterator>;
+
+ int* data_;
+ std::size_t size_;
+
+ Range() : data_(val), size_(4) {}
+
+ Range(int* data, std::size_t size) : data_(data), size_(size) {}
+
+ iterator begin() { return iterator(data_); }
+ iterator end() { return iterator(data_ + size_); }
+
+ const_iterator begin() const { return const_iterator(data_); }
+ const_iterator end() const { return const_iterator(data_ + size_); }
+};
+
+template <std::size_t N, typename T>
+struct NonSimpleIter {
+ using
diff erence_type = ptr
diff _t;
+ using value_type = T;
+
+ T* ptr_ = nullptr;
+
+ NonSimpleIter() = default;
+ NonSimpleIter(T* ptr) : ptr_(ptr) {}
+
+ template <class U>
+ requires(std::is_const_v<T> && !std::is_const_v<U> &&
+ std::is_same_v<std::remove_const_t<U>, std::remove_const_t<T>>)
+ NonSimpleIter(const NonSimpleIter<N, U>& other) : ptr_(other.ptr_) {}
+
+ NonSimpleIter(const NonSimpleIter&) = default;
+ NonSimpleIter(NonSimpleIter&& other) : ptr_(other.ptr_) {
+ if (flag)
+ throw 5;
+ }
+
+ NonSimpleIter& operator=(const NonSimpleIter&) = default;
+ NonSimpleIter& operator=(NonSimpleIter&& o) {
+ ptr_ = o.ptr_;
+ if (flag)
+ throw 5;
+ return *this;
+ }
+
+ T& operator*() const { return *ptr_; }
+ NonSimpleIter& operator++() {
+ ++ptr_;
+ return *this;
+ }
+ NonSimpleIter operator++(int) {
+ auto tmp = *this;
+ ++*this;
+ return tmp;
+ }
+
+ friend bool operator==(NonSimpleIter, NonSimpleIter) = default;
+};
+
+template <std::size_t N, typename T>
+struct NonSimpleRange : std::ranges::view_base {
+ NonSimpleIter<N, T> begin() { return &val[0]; }
+ NonSimpleIter<N, T> end() { return &val[3]; }
+ NonSimpleIter<N, const T> begin() const { return &val[0]; }
+ NonSimpleIter<N, const T> end() const { return &val[3]; }
+};
+
+static_assert(std::ranges::range<Range<0>>);
+static_assert(std::ranges::sized_range<Range<0>>);
+
+int main() {
+ {
+ // valueless by exception test operator*
+ Range<0> r1;
+ Range<1> r2;
+
+ auto cv = std::views::concat(r1, r2);
+ auto iter1 = cv.begin();
+ auto iter2 = std::ranges::next(cv.begin(), 4);
+ flag = true;
+ try {
+ iter1 = std::move(iter2);
+ assert(false);
+ } catch (...) {
+ TEST_LIBCPP_ASSERT_FAILURE([=] { (void)*iter1; }(), "Trying to dereference a valueless iterator of concat_view.");
+ }
+ }
+
+ {
+ // valueless by exception test operator==
+ flag = false;
+ Range<0> r1;
+ Range<1> r2;
+
+ auto cv = std::views::concat(r1, r2);
+ auto iter1 = cv.begin();
+ auto iter2 = std::ranges::next(cv.begin(), 4);
+ auto iter3 = cv.begin();
+ flag = true;
+ try {
+ iter1 = std::move(iter2);
+ assert(false);
+ } catch (...) {
+ TEST_LIBCPP_ASSERT_FAILURE(
+ [=] { (void)(iter1 == iter3); }(), "Trying to compare a valueless iterator of concat_view.");
+ }
+ }
+
+ {
+ // valueless by exception test operator== with a sentinel
+ flag = false;
+ Range<0> r1;
+ Range<1> r2;
+ auto cv = std::views::concat(r1, r2);
+ auto iter1 = cv.begin();
+ auto iter2 = std::ranges::next(cv.begin(), 4);
+ flag = true;
+ try {
+ iter1 = std::move(iter2);
+ assert(false);
+ } catch (...) {
+ TEST_LIBCPP_ASSERT_FAILURE([=] { (void)(iter1 == std::default_sentinel); }(),
+ "Trying to compare a valueless iterator of concat_view with the default sentinel.");
+ }
+ }
+
+ {
+ // valueless by exception test operator--
+ flag = false;
+ Range<0> r1;
+ Range<1> r2;
+
+ auto cv = std::views::concat(r1, r2);
+ auto iter1 = cv.begin();
+ auto iter2 = std::ranges::next(cv.begin(), 4);
+ flag = true;
+ try {
+ iter1 = std::move(iter2);
+ assert(false);
+ } catch (...) {
+ TEST_LIBCPP_ASSERT_FAILURE([&] { --iter1; }(), "Trying to decrement a valueless iterator of concat_view.");
+ }
+ }
+
+ {
+ // valueless by exception test operator--(int)
+ flag = false;
+ Range<0> r1;
+ Range<1> r2;
+
+ auto cv = std::views::concat(r1, r2);
+ auto iter1 = cv.begin();
+ auto iter2 = std::ranges::next(cv.begin(), 4);
+ flag = true;
+ try {
+ iter1 = std::move(iter2);
+ assert(false);
+ } catch (...) {
+ TEST_LIBCPP_ASSERT_FAILURE([&] { iter1--; }(), "Trying to decrement a valueless iterator of concat_view.");
+ }
+ }
+
+ {
+ // valueless by exception test operator++(int)
+ flag = false;
+ Range<0> r1;
+ Range<1> r2;
+
+ auto cv = std::views::concat(r1, r2);
+ auto iter1 = cv.begin();
+ auto iter2 = std::ranges::next(cv.begin(), 4);
+ flag = true;
+ try {
+ iter1 = std::move(iter2);
+ assert(false);
+ } catch (...) {
+ TEST_LIBCPP_ASSERT_FAILURE([&] { iter1++; }(), "Trying to increment a valueless iterator of concat_view.");
+ }
+ }
+
+ {
+ // valueless by exception test operator++
+ flag = false;
+ Range<0> r1;
+ Range<1> r2;
+
+ auto cv = std::views::concat(r1, r2);
+ auto iter1 = cv.begin();
+ auto iter2 = std::ranges::next(cv.begin(), 4);
+ flag = true;
+ try {
+ iter1 = std::move(iter2);
+ assert(false);
+ } catch (...) {
+ TEST_LIBCPP_ASSERT_FAILURE([&] { ++iter1; }(), "Trying to increment a valueless iterator of concat_view.");
+ }
+ }
+
+ {
+ // valueless by exception test operator+=
+ flag = false;
+ Range<0> r1;
+ Range<1> r2;
+
+ auto cv = std::views::concat(r1, r2);
+ auto iter1 = cv.begin();
+ auto iter2 = std::ranges::next(cv.begin(), 4);
+ flag = true;
+ try {
+ iter1 = std::move(iter2);
+ assert(false);
+ } catch (...) {
+ TEST_LIBCPP_ASSERT_FAILURE([&] { iter1 += 1; }(), "Trying to increment a valueless iterator of concat_view.");
+ }
+ }
+
+ {
+ // valueless by exception test operator-=
+ // this one eventually calls operator+= inside the function
+ flag = false;
+ Range<0> r1;
+ Range<1> r2;
+
+ auto cv = std::views::concat(r1, r2);
+ auto iter1 = cv.begin();
+ auto iter2 = std::ranges::next(cv.begin(), 4);
+ flag = true;
+ try {
+ iter1 = std::move(iter2);
+ assert(false);
+ } catch (...) {
+ TEST_LIBCPP_ASSERT_FAILURE([&] { iter1 -= 1; }(), "Trying to increment a valueless iterator of concat_view.");
+ }
+ }
+
+ {
+ // valueless by exception test operator-=
+ // this one eventually calls operator+= inside the function
+ flag = false;
+ Range<0> r1;
+ Range<1> r2;
+
+ auto cv = std::views::concat(r1, r2);
+ auto iter1 = cv.begin();
+ auto iter2 = std::ranges::next(cv.begin(), 4);
+ flag = true;
+ try {
+ iter1 = std::move(iter2);
+ assert(false);
+ } catch (...) {
+ TEST_LIBCPP_ASSERT_FAILURE([&] { (void)iter1[1]; }(), "Trying to increment a valueless iterator of concat_view.");
+ }
+ }
+
+ {
+ // valueless by exception test operator+(it, n)
+ flag = false;
+ Range<0> r1;
+ Range<1> r2;
+
+ auto cv = std::views::concat(r1, r2);
+ auto iter1 = cv.begin();
+ auto iter2 = std::ranges::next(cv.begin(), 4);
+ flag = true;
+ try {
+ iter1 = std::move(iter2);
+ assert(false);
+ } catch (...) {
+ TEST_LIBCPP_ASSERT_FAILURE([&] { [[maybe_unused]] auto iter3 = iter1 + 1; }(),
+ "Trying to increment a valueless iterator of concat_view.");
+ }
+ }
+
+ {
+ // valueless by exception test operator+(n, it)
+ flag = false;
+ Range<0> r1;
+ Range<1> r2;
+
+ auto cv = std::views::concat(r1, r2);
+ auto iter1 = cv.begin();
+ auto iter2 = std::ranges::next(cv.begin(), 4);
+ flag = true;
+ try {
+ iter1 = std::move(iter2);
+ assert(false);
+ } catch (...) {
+ TEST_LIBCPP_ASSERT_FAILURE([&] { [[maybe_unused]] auto iter3 = iter1 + 1; }(),
+ "Trying to increment a valueless iterator of concat_view.");
+ }
+ }
+
+ {
+ // valueless by exception test operator-(it, default_sentinel)
+ flag = false;
+ Range<0> r1;
+ Range<1> r2;
+
+ auto cv = std::views::concat(r1, r2);
+ auto iter1 = cv.begin();
+ auto iter2 = std::ranges::next(cv.begin(), 4);
+ flag = true;
+ try {
+ iter1 = std::move(iter2);
+ assert(false);
+ } catch (...) {
+ TEST_LIBCPP_ASSERT_FAILURE([&] { [[maybe_unused]] auto iter3 = iter1 - std::default_sentinel_t{}; }(),
+ "Trying to subtract a valuess iterators of concat_view from the default sentinel.");
+ }
+ }
+
+ {
+ // valueless by exception test operator-(default_sentinel, it)
+ flag = false;
+ Range<0> r1;
+ Range<1> r2;
+
+ auto cv = std::views::concat(r1, r2);
+ auto iter1 = cv.begin();
+ auto iter2 = std::ranges::next(cv.begin(), 4);
+ flag = true;
+ try {
+ iter1 = std::move(iter2);
+ assert(false);
+ } catch (...) {
+ TEST_LIBCPP_ASSERT_FAILURE([&] { [[maybe_unused]] auto iter3 = iter1 - std::default_sentinel_t{}; }(),
+ "Trying to subtract a valuess iterators of concat_view from the default sentinel.");
+ }
+ }
+
+ {
+ // valueless by exception test operator>
+ flag = false;
+ Range<0> r1;
+ Range<1> r2;
+
+ auto cv = std::views::concat(r1, r2);
+ auto iter1 = cv.begin();
+ auto iter2 = std::ranges::next(cv.begin(), 4);
+ flag = true;
+ try {
+ iter1 = std::move(iter2);
+ assert(false);
+ } catch (...) {
+ TEST_LIBCPP_ASSERT_FAILURE(
+ [&] { (void)(iter1 > iter2); }(), "Trying to compare a valueless iterator of concat_view.");
+ }
+ }
+
+ {
+ // valueless by exception test operator>=
+ flag = false;
+ Range<0> r1;
+ Range<1> r2;
+
+ auto cv = std::views::concat(r1, r2);
+ auto iter1 = cv.begin();
+ auto iter2 = std::ranges::next(cv.begin(), 4);
+ flag = true;
+ try {
+ iter1 = std::move(iter2);
+ assert(false);
+ } catch (...) {
+ TEST_LIBCPP_ASSERT_FAILURE(
+ [&] { (void)(iter1 >= iter2); }(), "Trying to compare a valueless iterator of concat_view.");
+ }
+ }
+
+ {
+ // valueless by exception test operator<
+ flag = false;
+ Range<0> r1;
+ Range<1> r2;
+
+ auto cv = std::views::concat(r1, r2);
+ auto iter1 = cv.begin();
+ auto iter2 = std::ranges::next(cv.begin(), 4);
+ flag = true;
+ try {
+ iter1 = std::move(iter2);
+ assert(false);
+ } catch (...) {
+ TEST_LIBCPP_ASSERT_FAILURE(
+ [&] { (void)(iter1 < iter2); }(), "Trying to compare a valueless iterator of concat_view.");
+ }
+ }
+
+ {
+ // valueless by exception test operator<=
+ flag = false;
+ Range<0> r1;
+ Range<1> r2;
+
+ auto cv = std::views::concat(r1, r2);
+ auto iter1 = cv.begin();
+ auto iter2 = std::ranges::next(cv.begin(), 4);
+ flag = true;
+ try {
+ iter1 = std::move(iter2);
+ assert(false);
+ } catch (...) {
+ TEST_LIBCPP_ASSERT_FAILURE(
+ [&] { (void)(iter1 <= iter2); }(), "Trying to compare a valueless iterator of concat_view.");
+ }
+ }
+
+ {
+ // valueless by exception test operator<=>
+ flag = false;
+ Range<0> r1;
+ Range<1> r2;
+
+ auto cv = std::views::concat(r1, r2);
+ auto iter1 = cv.begin();
+ auto iter2 = std::ranges::next(cv.begin(), 4);
+ flag = true;
+ try {
+ iter1 = std::move(iter2);
+ assert(false);
+ } catch (...) {
+ TEST_LIBCPP_ASSERT_FAILURE(
+ [&] { (void)(iter1 <=> iter2); }(), "Trying to compare a valueless iterator of concat_view.");
+ }
+ }
+
+ {
+ // valueless by exception test operator- between two iterators
+ flag = false;
+ Range<0> r1;
+ Range<1> r2;
+
+ auto cv = std::views::concat(r1, r2);
+ auto iter1 = cv.begin();
+ auto iter2 = std::ranges::next(cv.begin(), 4);
+ flag = true;
+ try {
+ iter1 = std::move(iter2);
+ assert(false);
+ } catch (...) {
+ TEST_LIBCPP_ASSERT_FAILURE(
+ [&] { (void)(iter1 - iter2); }(),
+ "Trying to subtract two iterators of concat_view where at least one iterator is valueless.");
+ }
+ }
+
+ {
+ // valueless by exception test operator- with a constant
+ flag = false;
+ Range<0> r1;
+ Range<1> r2;
+
+ auto cv = std::views::concat(r1, r2);
+ auto iter1 = cv.begin();
+ auto iter2 = std::ranges::next(cv.begin(), 4);
+ flag = true;
+ try {
+ iter1 = std::move(iter2);
+ } catch (...) {
+ TEST_LIBCPP_ASSERT_FAILURE(
+ [&] { (void)(iter1 - 1); }(), "Trying to subtract a valuess iterators of concat_view.");
+ }
+ }
+
+ {
+ // valueless by exception test iter_move(it)
+ flag = false;
+ Range<0> r1;
+ Range<1> r2;
+
+ auto cv = std::views::concat(r1, r2);
+ auto iter1 = cv.begin();
+ auto iter2 = std::ranges::next(cv.begin(), 4);
+ flag = true;
+ try {
+ iter1 = std::move(iter2);
+ assert(false);
+ } catch (...) {
+ TEST_LIBCPP_ASSERT_FAILURE([&] { [[maybe_unused]] auto iter3 = std::ranges::iter_move(iter1); }(),
+ "Trying to apply iter_move to a valueless iterator of concat_view.");
+ }
+ }
+
+ {
+ // valueless by exception test iter_swap(iter1, iter2)
+ flag = false;
+ Range<0> r1;
+ Range<1> r2;
+
+ auto cv = std::views::concat(r1, r2);
+ auto iter1 = cv.begin();
+ auto iter2 = std::ranges::next(cv.begin(), 4);
+ flag = true;
+ try {
+ iter1 = std::move(iter2);
+ assert(false);
+ } catch (...) {
+ TEST_LIBCPP_ASSERT_FAILURE([&] { std::ranges::iter_swap(iter1, iter2); }(),
+ "Trying to swap iterators of concat_view where at least one iterator is valueless.");
+ }
+ }
+
+ {
+ // valueless by exception test constructor
+ flag = false;
+ NonSimpleRange<0, int> r1;
+ NonSimpleRange<1, int> r2;
+
+ auto cv = std::views::concat(r1, r2);
+ auto iter1 = cv.begin();
+ auto iter2 = std::ranges::next(cv.begin(), 4);
+ flag = true;
+ using Iter = std::ranges::iterator_t<decltype(cv)>; // iterator<false>
+ using CIter = std::ranges::iterator_t<const decltype(cv)>; // iterator<true>
+
+ try {
+ iter1 = std::move(iter2);
+ assert(false);
+ } catch (...) {
+ TEST_LIBCPP_ASSERT_FAILURE(
+ [&] { [[maybe_unused]] CIter it3(iter1); }(), "Trying to convert from a valueless iterator of concat_view.");
+ }
+ }
+}
diff --git a/libcxx/test/libcxx/ranges/range.adaptors/range.concat/nodiscard.verify.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.concat/nodiscard.verify.cpp
new file mode 100644
index 0000000000000..b81e646d381c4
--- /dev/null
+++ b/libcxx/test/libcxx/ranges/range.adaptors/range.concat/nodiscard.verify.cpp
@@ -0,0 +1,70 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// Test that concat_view and its iterator's functions are marked [[nodiscard]].
+
+#include <array>
+#include <ranges>
+#include <utility>
+#include <vector>
+
+void test() {
+ std::array<int, 3> a{1, 2, 3};
+ std::vector<int> b{4, 5, 6};
+
+ std::ranges::concat_view cv(a, b);
+
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ cv.begin();
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ std::as_const(cv).begin();
+
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ cv.end();
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ std::as_const(cv).end();
+
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ cv.size();
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ std::as_const(cv).size();
+
+ // [range.concat.iterator]
+
+ auto it = cv.begin();
+
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ *it;
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ it[2];
+
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ it + 1;
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ 1 + it;
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ it - 1;
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ it - it;
+
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ it - std::default_sentinel;
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ std::default_sentinel - it;
+
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ iter_move(it);
+
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ std::views::concat(a);
+
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ std::views::concat(a, b);
+}
diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/ranges.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/ranges.version.compile.pass.cpp
index 6825f9675d459..670b0e664721a 100644
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/ranges.version.compile.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/ranges.version.compile.pass.cpp
@@ -447,17 +447,11 @@
# error "__cpp_lib_ranges_chunk_by should have the value 202202L in c++26"
# endif
-# if !defined(_LIBCPP_VERSION)
-# ifndef __cpp_lib_ranges_concat
-# error "__cpp_lib_ranges_concat should be defined in c++26"
-# endif
-# if __cpp_lib_ranges_concat != 202403L
-# error "__cpp_lib_ranges_concat should have the value 202403L in c++26"
-# endif
-# else
-# ifdef __cpp_lib_ranges_concat
-# error "__cpp_lib_ranges_concat should not be defined because it is unimplemented in libc++!"
-# endif
+# ifndef __cpp_lib_ranges_concat
+# error "__cpp_lib_ranges_concat should be defined in c++26"
+# endif
+# if __cpp_lib_ranges_concat != 202403L
+# error "__cpp_lib_ranges_concat should have the value 202403L in c++26"
# endif
# ifndef __cpp_lib_ranges_enumerate
diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
index dfee4b6d458db..2aa52a64c6cf0 100644
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
@@ -7691,17 +7691,11 @@
# error "__cpp_lib_ranges_chunk_by should have the value 202202L in c++26"
# endif
-# if !defined(_LIBCPP_VERSION)
-# ifndef __cpp_lib_ranges_concat
-# error "__cpp_lib_ranges_concat should be defined in c++26"
-# endif
-# if __cpp_lib_ranges_concat != 202403L
-# error "__cpp_lib_ranges_concat should have the value 202403L in c++26"
-# endif
-# else
-# ifdef __cpp_lib_ranges_concat
-# error "__cpp_lib_ranges_concat should not be defined because it is unimplemented in libc++!"
-# endif
+# ifndef __cpp_lib_ranges_concat
+# error "__cpp_lib_ranges_concat should be defined in c++26"
+# endif
+# if __cpp_lib_ranges_concat != 202403L
+# error "__cpp_lib_ranges_concat should have the value 202403L in c++26"
# endif
# ifndef __cpp_lib_ranges_contains
diff --git a/libcxx/test/std/library/description/conventions/customization.point.object/cpo.compile.pass.cpp b/libcxx/test/std/library/description/conventions/customization.point.object/cpo.compile.pass.cpp
index 9443ca6c90a30..d83ca7f2eabfc 100644
--- a/libcxx/test/std/library/description/conventions/customization.point.object/cpo.compile.pass.cpp
+++ b/libcxx/test/std/library/description/conventions/customization.point.object/cpo.compile.pass.cpp
@@ -137,6 +137,6 @@ static_assert(test(std::views::zip, a, a));
#if TEST_STD_VER >= 26
// static_assert(test(std::views::cache_latest, a));
-// static_assert(test(std::views::concat, a, a));
+static_assert(test(std::views::concat, a, a));
// static_assert(test(std::views::to_input, a));
#endif
diff --git a/libcxx/test/std/ranges/range.adaptors/range.concat/adaptor.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.concat/adaptor.pass.cpp
new file mode 100644
index 0000000000000..b0358856a07c8
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.concat/adaptor.pass.cpp
@@ -0,0 +1,73 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// std::views::concat
+
+#include <ranges>
+
+#include <array>
+#include <cassert>
+#include <iterator>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include "../range_adaptor_types.h"
+
+static_assert(!std::is_invocable_v<decltype((std::views::concat))>);
+static_assert(!std::is_invocable_v<decltype((std::views::concat)), int>);
+static_assert(std::is_invocable_v<decltype((std::views::concat)), SizedRandomAccessView>);
+static_assert(std::is_invocable_v<decltype((std::views::concat)), SizedRandomAccessView, NonSimpleCommon>);
+static_assert(std::is_invocable_v<decltype((std::views::concat)),
+ SizedRandomAccessView,
+ NonSimpleCommon,
+ NonSimpleBidiCommonView>);
+static_assert(!std::is_invocable_v<decltype((std::views::concat)), SizedRandomAccessView, int>);
+
+constexpr bool test() {
+ {
+ // single range
+ int buffer[8] = {1, 2, 3, 4, 5, 6, 7, 8};
+ std::same_as<decltype(std::views::all((std::forward<SizedRandomAccessView>(buffer))))> decltype(auto) v =
+ std::ranges::views::concat(SizedRandomAccessView{buffer});
+ assert(std::ranges::size(v) == 8);
+ static_assert(std::is_same_v<std::ranges::range_reference_t<decltype(v)>, int&>);
+ }
+
+ {
+ // single view as output range will be rejected
+ // https://cplusplus.github.io/LWG/issue4082
+ std::vector<int> v{1, 2, 3};
+ static_assert(
+ !std::is_invocable_v<decltype((std::views::concat)), decltype(std::views::counted(std::back_inserter(v), 3))>);
+ }
+
+ {
+ // more than one ranges
+ int buffer[4] = {1, 2, 3, 4};
+ std::array<int, 3> a{1, 2, 3};
+ std::same_as<std::ranges::concat_view<NonSimpleCommonRandomAccessSized,
+ std::ranges::ref_view<std::array<int, 3>>>> decltype(auto) v =
+ std::ranges::views::concat(NonSimpleCommonRandomAccessSized{buffer}, a);
+ assert(&(*v.begin()) == &(buffer[0]));
+ assert(&(*(v.begin() + 4)) == &(a[0]));
+ static_assert(std::is_same_v<std::ranges::range_reference_t<decltype(v)>, int&>);
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.concat/begin.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.concat/begin.pass.cpp
new file mode 100644
index 0000000000000..0c6081fd70c4f
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.concat/begin.pass.cpp
@@ -0,0 +1,119 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// constexpr __iterator<false> begin()
+// requires(!(__simple_view<_Views> && ...))
+
+// constexpr __iterator<true> begin() const
+// requires((range<const _Views> && ...) && __concatable<const _Views...>)
+
+#include <array>
+#include <ranges>
+#include <vector>
+
+#include <cassert>
+#include "test_iterators.h"
+#include "types.h"
+#include "../range_adaptor_types.h"
+
+template <class T>
+concept HasConstBegin = requires(const T& ct) { ct.begin(); };
+
+template <class T>
+concept HasBegin = requires(T& t) { t.begin(); };
+
+template <class T>
+concept HasConstAndNonConstBegin = HasConstBegin<T> && requires(T& t, const T& ct) {
+ requires !std::same_as<decltype(t.begin()), decltype(ct.begin())>;
+};
+
+template <class T>
+concept HasOnlyNonConstBegin = HasBegin<T> && !HasConstBegin<T>;
+
+template <class T>
+concept HasOnlyConstBegin = HasConstBegin<T> && !HasConstAndNonConstBegin<T>;
+
+constexpr bool test() {
+ // check the case of simple view
+ {
+ int buffer[4] = {1, 2, 3, 4};
+ std::ranges::concat_view v(SimpleCommon{buffer}, SimpleCommon{buffer});
+ static_assert(std::is_same_v<decltype(v.begin()), decltype(std::as_const(v).begin())>);
+ assert(v.begin() == std::as_const(v).begin());
+ assert(*v.begin() == buffer[0]);
+ assert(*std::as_const(v).begin() == buffer[0]);
+
+ using View = decltype(v);
+ static_assert(HasOnlyConstBegin<View>);
+ static_assert(!HasOnlyNonConstBegin<View>);
+ static_assert(!HasConstAndNonConstBegin<View>);
+ }
+
+ // not all underlying ranges model simple view
+ {
+ int buffer[4] = {1, 2, 3, 4};
+ std::ranges::concat_view v(SimpleCommon{buffer}, NonSimpleNonCommon{buffer});
+ static_assert(!std::is_same_v<decltype(v.begin()), decltype(std::as_const(v).begin())>);
+ assert(v.begin() == std::as_const(v).begin());
+ assert(*v.begin() == buffer[0]);
+ assert(*std::as_const(v).begin() == buffer[0]);
+
+ using View = decltype(v);
+ static_assert(!HasOnlyConstBegin<View>);
+ static_assert(!HasOnlyNonConstBegin<View>);
+ static_assert(HasConstAndNonConstBegin<View>);
+ }
+
+ // first view is empty
+ {
+ std::vector<int> v1;
+ std::vector<int> v2 = {1, 2, 3, 4};
+ std::ranges::concat_view view(v1, v2);
+ auto it = view.begin();
+ assert(*it == 1);
+ assert(it + 4 == view.end());
+ }
+
+ // first few views is empty, including
diff erent types
+ {
+ std::vector<int> v1;
+ std::array<int, 0> v2;
+ std::vector<int> v3 = {1, 2, 3, 4};
+ std::ranges::concat_view view(v1, v2, v3);
+ auto it = view.begin();
+ assert(*it == 1);
+ assert(it + 4 == view.end());
+ }
+
+ // all views are empty
+ {
+ std::array<int, 0> arr;
+ std::vector<int> v;
+ std::ranges::concat_view(arr, v);
+ assert(v.begin() == v.end());
+ }
+
+ // testing concatable constraint
+ {
+ static_assert(!ConcatableConstViews<ViewWithNoConstBegin>);
+ static_assert(ConcatableConstViews<ViewWithConstBegin>);
+ static_assert(!ConcatableConstViews<ViewWithNoConstBegin, ViewWithConstBegin>);
+ static_assert(!ConcatableConstViews<ViewWithNoConstBegin, ViewWithConstBegin, SizedViewWithConstBegin>);
+ static_assert(ConcatableConstViews<ViewWithConstBegin, SizedViewWithConstBegin>);
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.concat/constraints.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.concat/constraints.pass.cpp
new file mode 100644
index 0000000000000..0792c8c7c921a
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.concat/constraints.pass.cpp
@@ -0,0 +1,288 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+#include <cassert>
+#include <list>
+#include <ranges>
+#include <string>
+#include <type_traits>
+#include <vector>
+#include "test_iterators.h"
+#include "test_macros.h"
+
+// test concept constraints
+
+template <typename T>
+concept WellFormedView = requires(T& a) { std::views::concat(a); };
+
+struct X {};
+struct Y {};
+
+struct BadIter {
+ using value_type = int;
+ int* p;
+
+ BadIter() = default;
+ explicit BadIter(int* q) : p(q) {}
+
+ int& operator*() const { return *p; }
+ BadIter& operator++() {
+ ++p;
+ return *this;
+ }
+ void operator++(int) { ++p; }
+
+ friend bool operator==(const BadIter& a, const BadIter& b) { return a.p == b.p; }
+ friend bool operator!=(const BadIter& a, const BadIter& b) { return !(a == b); }
+
+ friend X iter_move(const BadIter&) { return X{}; }
+};
+
+struct BadView : std::ranges::view_base {
+ int buf_[1] = {0};
+ BadIter begin() const { return BadIter(const_cast<int*>(buf_)); }
+ BadIter end() const { return BadIter(const_cast<int*>(buf_ + 1)); }
+};
+
+struct InputRange {
+ using Iterator = cpp17_input_iterator<int*>;
+ using Sentinel = sentinel_wrapper<Iterator>;
+ constexpr InputRange(int* b, int* e) : begin_(b), end_(e) {}
+ constexpr Iterator begin() { return Iterator(begin_); }
+ constexpr Sentinel end() { return Sentinel(Iterator(end_)); }
+
+private:
+ int* begin_;
+ int* end_;
+};
+
+struct RefOnlyRange1 : std::ranges::view_base {
+ X* begin() const;
+ X* end() const;
+};
+
+struct RefOnlyRange2 : std::ranges::view_base {
+ Y* begin() const;
+ Y* end() const;
+};
+
+namespace std {
+template <template <class> class TQual, template <class> class UQual>
+struct basic_common_reference< X, Y, TQual, UQual> {
+ using type = X;
+};
+
+template <template <class> class TQual, template <class> class UQual>
+struct basic_common_reference< Y, X, TQual, UQual> {
+ using type = X;
+};
+} // namespace std
+
+struct R1 : std::ranges::view_base {
+ int* first = nullptr;
+ int* last = nullptr;
+
+ R1() = default;
+ R1(int* f, int* l) : first(f), last(l) {}
+
+ struct iterator {
+ using value_type = int;
+ using
diff erence_type = std::ptr
diff _t;
+ using iterator_concept = std::forward_iterator_tag;
+
+ int* p = nullptr;
+
+ int& operator*() const { return *p; }
+ iterator& operator++() {
+ ++p;
+ return *this;
+ }
+ iterator operator++(int) {
+ auto tmp = *this;
+ ++*this;
+ return tmp;
+ }
+
+ friend bool operator==(const iterator& x, const iterator& y) { return x.p == y.p; }
+
+ friend X iter_move(const iterator& it) {
+ (void)it;
+ return X{};
+ }
+ };
+
+ iterator begin() const { return iterator{first}; }
+ iterator end() const { return iterator{last}; }
+};
+
+struct R2 : std::ranges::view_base {
+ int* first = nullptr;
+ int* last = nullptr;
+
+ R2() = default;
+ R2(int* f, int* l) : first(f), last(l) {}
+
+ struct iterator {
+ using value_type = int;
+ using
diff erence_type = std::ptr
diff _t;
+ using iterator_concept = std::forward_iterator_tag;
+
+ int* p = nullptr;
+
+ int& operator*() const { return *p; }
+ iterator& operator++() {
+ ++p;
+ return *this;
+ }
+ iterator operator++(int) {
+ auto tmp = *this;
+ ++*this;
+ return tmp;
+ }
+
+ friend bool operator==(const iterator& x, const iterator& y) { return x.p == y.p; }
+
+ friend Y iter_move(const iterator& it) {
+ (void)it;
+ return Y{};
+ }
+ };
+
+ iterator begin() const { return iterator{first}; }
+ iterator end() const { return iterator{last}; }
+};
+
+struct MoveOnlyIterator {
+ using It = int*;
+
+ It it_;
+
+ using iterator_category = std::input_iterator_tag;
+ using value_type = int;
+ using
diff erence_type = std::ptr
diff _t;
+ using reference = int&;
+
+ constexpr explicit MoveOnlyIterator(It it) : it_(it) {}
+ MoveOnlyIterator(MoveOnlyIterator&&) = default;
+ MoveOnlyIterator& operator=(MoveOnlyIterator&&) = default;
+ MoveOnlyIterator(const MoveOnlyIterator&) = delete;
+ MoveOnlyIterator& operator=(const MoveOnlyIterator&) = delete;
+
+ constexpr reference operator*() const { return *it_; }
+
+ constexpr MoveOnlyIterator& operator++() {
+ ++it_;
+ return *this;
+ }
+ constexpr MoveOnlyIterator operator++(int) { return MoveOnlyIterator(it_++); }
+
+ friend constexpr bool operator==(const MoveOnlyIterator& x, const MoveOnlyIterator& y) { return x.it_ == y.it_; }
+ friend constexpr bool operator!=(const MoveOnlyIterator& x, const MoveOnlyIterator& y) { return x.it_ != y.it_; }
+};
+
+struct MoveOnlyView : std::ranges::view_base {
+ int* b;
+ int* e;
+ constexpr MoveOnlyView() = default;
+ constexpr MoveOnlyView(int* b_, int* e_) : b(b_), e(e_) {}
+ MoveOnlyView(const MoveOnlyView&) = delete;
+ constexpr MoveOnlyView(MoveOnlyView&& other) : b(other.b), e(other.e) {}
+ MoveOnlyView& operator=(const MoveOnlyView&) = delete;
+ constexpr MoveOnlyView& operator=(MoveOnlyView&& other) {
+ b = other.b;
+ e = other.e;
+ return *this;
+ }
+
+ constexpr auto begin() const { return MoveOnlyIterator{b}; }
+ constexpr auto end() const { return MoveOnlyIterator{e}; }
+};
+
+template <typename... Ts>
+concept ConcatViewConstraintsPass = requires(Ts&&... a) { std::views::concat(a...); };
+
+int main(int, char**) {
+ // rejects when it is an output range
+ {
+ std::vector<int> v{1, 2, 3};
+ static_assert(!WellFormedView<decltype(std::views::counted(std::back_inserter(v), 3))>);
+ }
+
+ // input range
+ {
+ static_assert(WellFormedView<InputRange>);
+ }
+
+ // bidirectional range
+ {
+ static_assert(WellFormedView<std::list<int>>);
+ }
+
+ // random access range
+ {
+ static_assert(WellFormedView<std::vector<int>>);
+ }
+
+ {
+ // LWG 4082
+ std::vector<int> v{1, 2, 3};
+ auto r = std::views::counted(std::back_inserter(v), 3);
+ //auto c = std::views::concat(r);
+ static_assert(!ConcatViewConstraintsPass<decltype(r)>);
+ }
+
+ {
+ // input is a view and has 0 size
+ static_assert(!ConcatViewConstraintsPass<>);
+ }
+
+ {
+ // input is a view and has at least an element
+ static_assert(ConcatViewConstraintsPass<std::vector<int>>);
+ }
+
+ {
+ // inputs are non-concatable
+ static_assert(!ConcatViewConstraintsPass<std::vector<int>, std::vector<std::string>>);
+ }
+
+ {
+ // test concept concatable
+ {
+ // concat-reference-t is ill-formed
+ // no common reference between int and string
+ static_assert(!ConcatViewConstraintsPass<std::array<int, 1>, std::array<std::string, 1>>);
+ }
+
+ {
+ // concat-value-t is ill-formed but concat-reference-t is valid
+ static_assert(!ConcatViewConstraintsPass<RefOnlyRange1, RefOnlyRange2>);
+ }
+
+ {
+ // concat-rvalue-reference-t is ill-formed
+ static_assert(!ConcatViewConstraintsPass<R1, R2>);
+ }
+
+ {
+ // concat-indirectly-readable is ill-formed
+ static_assert(!ConcatViewConstraintsPass<BadView, BadView>);
+ }
+
+ {
+ // concatable fails when there is a MoveOnly& and MoveOnly
+ // Let Fs be a pack containing MoveOnly& and MoveOnly
+ // common_reference_with<concat_reference_t<Fs...>, concat_value_t<Fs...>> fails
+ static_assert(!ConcatViewConstraintsPass<MoveOnlyView&, MoveOnlyView>);
+ }
+ }
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.concat/ctad.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.concat/ctad.pass.cpp
new file mode 100644
index 0000000000000..900696db9212c
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.concat/ctad.pass.cpp
@@ -0,0 +1,65 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// template <class... _Views>
+// concat_view(_Views&&...) -> concat_view<views::all_t<_Views>...>;
+
+#include <cassert>
+#include <ranges>
+#include <type_traits>
+
+#include "test_iterators.h"
+
+struct View : std::ranges::view_base {
+ View() = default;
+ forward_iterator<int*> begin() const;
+ sentinel_wrapper<forward_iterator<int*>> end() const;
+};
+static_assert(std::ranges::view<View>);
+
+// A range that is not a view
+struct Range {
+ Range() = default;
+ forward_iterator<int*> begin() const;
+ sentinel_wrapper<forward_iterator<int*>> end() const;
+};
+static_assert(std::ranges::range<Range> && !std::ranges::view<Range>);
+
+constexpr bool test() {
+ {
+ View v;
+ std::ranges::concat_view view(v);
+ static_assert(std::is_same_v<decltype(view), std::ranges::concat_view<View>>);
+ }
+
+ // Test with a range that isn't a view, to make sure we properly use views::all_t in the implementation.
+ {
+ Range r;
+ std::ranges::concat_view view(r);
+ static_assert(std::is_same_v<decltype(view), std::ranges::concat_view<std::ranges::ref_view<Range>>>);
+ }
+
+ // Test a view which has a range and a view
+ {
+ Range r;
+ View v;
+ std::ranges::concat_view view(r, v);
+ static_assert(std::is_same_v<decltype(view), std::ranges::concat_view<std::ranges::ref_view<Range>, View>>);
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.concat/ctor.default.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.concat/ctor.default.pass.cpp
new file mode 100644
index 0000000000000..bdf290cdfe026
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.concat/ctor.default.pass.cpp
@@ -0,0 +1,77 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// constexpr concat_view() = default;
+
+#include <cassert>
+#include <ranges>
+#include <type_traits>
+#include <vector>
+
+constexpr int buff[4] = {0, 1, 2, 3};
+
+struct DefaultConstructibleView : std::ranges::view_base {
+ constexpr DefaultConstructibleView() : begin_(buff), end_(buff + 4) {}
+ constexpr int const* begin() const { return begin_; }
+ constexpr int const* end() const { return end_; }
+ constexpr auto size() const { return 4; }
+
+private:
+ int const* begin_;
+ int const* end_;
+};
+
+struct NoDefaultView : std::ranges::view_base {
+ NoDefaultView() = delete;
+ int* begin() const;
+ int* end() const;
+};
+
+using DefaultView = std::ranges::concat_view<DefaultConstructibleView, DefaultConstructibleView>;
+using DefaultViewWithDiffTypes =
+ std::ranges::concat_view<DefaultConstructibleView, decltype(std::views::all(std::declval<std::vector<int>>()))>;
+using BadView1 = std::ranges::concat_view<DefaultConstructibleView, NoDefaultView>;
+using BadView2 = std::ranges::concat_view<NoDefaultView, NoDefaultView>;
+
+constexpr bool test() {
+ static_assert(std::is_default_constructible_v<DefaultView>);
+ static_assert(std::is_default_constructible_v<DefaultViewWithDiffTypes>);
+ static_assert(!std::is_default_constructible_v<BadView1>);
+ static_assert(!std::is_default_constructible_v<BadView2>);
+
+ {
+ DefaultView view = DefaultView();
+ assert(view.size() == 8);
+ auto it = view.begin();
+ assert(*it++ == 0);
+ assert(*it++ == 1);
+ assert(*it++ == 2);
+ assert(*it++ == 3);
+ }
+
+ {
+ DefaultViewWithDiffTypes view = DefaultViewWithDiffTypes();
+ assert(view.size() == 4);
+ auto it = view.begin();
+ assert(*it++ == 0);
+ assert(*it++ == 1);
+ assert(*it++ == 2);
+ assert(*it++ == 3);
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.concat/ctor.views.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.concat/ctor.views.pass.cpp
new file mode 100644
index 0000000000000..20afe84bcf980
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.concat/ctor.views.pass.cpp
@@ -0,0 +1,114 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// constexpr explicit concat_view(Views... views)
+
+#include <array>
+#include <cassert>
+#include <ranges>
+
+#include "../range_adaptor_types.h"
+
+template <class T>
+void conversion_test(T);
+
+template <class T, class... Args>
+concept implicitly_constructible_from = requires(Args&&... args) { conversion_test<T>({std::move(args)...}); };
+
+// test constructor is explicit
+static_assert(
+ std::constructible_from<std::ranges::concat_view<SimpleCommon, NonSimpleCommon>, SimpleCommon, NonSimpleCommon>);
+static_assert(!implicitly_constructible_from<std::ranges::concat_view<SimpleCommon, NonSimpleCommon>,
+ SimpleCommon,
+ NonSimpleCommon>);
+
+struct MoveAwareView : std::ranges::view_base {
+ int moves = 0;
+ constexpr MoveAwareView() = default;
+ constexpr MoveAwareView(MoveAwareView&& other) : moves(other.moves + 1) { other.moves = 1; }
+ constexpr MoveAwareView& operator=(MoveAwareView&& other) {
+ moves = other.moves + 1;
+ other.moves = 0;
+ return *this;
+ }
+ constexpr int* begin() { return &moves; }
+ constexpr int* end() { return &moves + 1; }
+};
+
+constexpr bool test() {
+ int buffer[3] = {1, 2, 3};
+ int buffer2[2] = {4, 5};
+
+ {
+ // constructor from views
+ std::ranges::concat_view v(SizedRandomAccessView{buffer}, std::array<int, 1>{7});
+ auto it = v.begin();
+ assert(*it == 1);
+ it++;
+ it++;
+ it++;
+ assert(*it == 7);
+ }
+
+ {
+ // arguments are moved
+ MoveAwareView mv;
+ std::ranges::concat_view v{std::move(mv), MoveAwareView{}};
+ auto it = v.begin();
+ assert(*it == 2);
+ it++;
+ assert(*it == 1);
+ }
+
+ {
+ //input and forward range
+ std::ranges::concat_view v(InputCommonView{buffer}, ForwardSizedView{buffer2});
+ auto it = v.begin();
+ assert(*it == 1);
+ it++;
+ assert(*it == 2);
+ it++;
+ assert(*it == 3);
+ it++;
+ assert(*it == 4);
+ it++;
+ assert(*it == 5);
+ it++;
+ }
+
+ {
+ // bidi
+ std::ranges::concat_view v(BidiCommonView{buffer}, SizedBidiCommon{buffer2});
+ auto it = v.begin();
+ assert(*it == 1);
+ it++;
+ assert(*it == 2);
+ it--;
+ assert(*it == 1);
+ }
+
+ {
+ // random access
+ std::ranges::concat_view v(SimpleCommonRandomAccessSized{buffer}, SizedRandomAccessView{buffer2});
+ auto it = v.begin();
+ assert(*it == 1);
+ assert(it[2] == 3);
+ assert(it[3] == 4);
+ }
+
+ return true;
+}
+
+int main() {
+ test();
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.concat/end.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.concat/end.pass.cpp
new file mode 100644
index 0000000000000..4d5cd7fbf6412
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.concat/end.pass.cpp
@@ -0,0 +1,116 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// constexpr auto end()
+// requires(!(__simple_view<_Views> && ...))
+
+// constexpr auto end() const
+// requires((range<const _Views> && ...) && __concatable<const _Views...>)
+
+#include <array>
+#include <cassert>
+#include <ranges>
+
+#include "test_iterators.h"
+#include "types.h"
+#include "../range_adaptor_types.h"
+
+constexpr bool test() {
+ int buffer1[5] = {1, 2, 3, 4, 5};
+ int buffer2[2] = {6, 7};
+
+ {
+ std::ranges::concat_view v(SimpleCommon{buffer1}, SimpleCommon{buffer2});
+ static_assert(std::is_same_v<decltype(v.end()), decltype(std::as_const(v).end())>);
+ static_assert(std::ranges::common_range<decltype(v)>);
+ assert(v.begin() + 7 == v.end());
+ static_assert(std::is_same_v<decltype(v.end()), decltype(std::as_const(v).end())>);
+ }
+
+ {
+ std::ranges::concat_view v(SimpleCommon{buffer1}, SimpleNonCommon{buffer2});
+ assert(v.begin() + 7 == v.end());
+ static_assert(std::is_same_v<decltype(v.end()), decltype(std::as_const(v).end())>);
+ static_assert(std::is_same_v<decltype(v.end()), std::default_sentinel_t>);
+ }
+
+ {
+ std::ranges::concat_view v(SimpleCommon{buffer1}, NonSimpleCommon{buffer2});
+ assert(v.begin() + 7 == v.end());
+ static_assert(!std::is_same_v<decltype(v.end()), decltype(std::as_const(v).end())>);
+ }
+
+ {
+ std::ranges::concat_view v(SimpleCommon{buffer1}, NonSimpleNonCommon{buffer2});
+ assert(v.begin() + 7 == v.end());
+ static_assert(std::is_same_v<decltype(v.end()), decltype(std::as_const(v).end())>);
+ static_assert(std::is_same_v<decltype(v.end()), std::default_sentinel_t>);
+ }
+
+ {
+ // all the ranges but the last one are input ranges, the last range is common => end() returns sentinel
+ // https://cplusplus.github.io/LWG/issue4166
+
+ using Iter = cpp20_input_iterator<const int*>;
+ using Sentinel = sentinel_wrapper<Iter>;
+ using InputView = minimal_view<Iter, Sentinel>;
+
+ std::array<int, 3> arr{1, 2, 3};
+ auto v = std::views::concat(InputView{Iter{buffer1}, Sentinel{Iter{buffer1 + 5}}}, arr);
+ static_assert(std::same_as<decltype(v.end()), std::default_sentinel_t>);
+ auto it = v.begin();
+ it++;
+ it++;
+ it++;
+ it++;
+ it++;
+ it++;
+ it++;
+ it++;
+ assert(it == v.end());
+ }
+
+ {
+ // first range is forward range, the last range is common => end() does not return sentinel
+ using Iter = forward_iterator<const int*>;
+ using Sentinel = sentinel_wrapper<Iter>;
+ using ForwardView = minimal_view<Iter, Sentinel>;
+
+ std::array<int, 2> arr{6, 7};
+ auto v = std::views::concat(ForwardView{Iter{buffer1}, Sentinel{Iter{buffer1 + 5}}}, arr);
+ static_assert(!std::same_as<decltype(v.end()), std::default_sentinel_t>);
+ static_assert(std::same_as<decltype(v.end()), decltype(v.begin())>);
+
+ auto it = v.begin();
+ auto last = v.end();
+ int sum = 0;
+ for (; it != last; it++) {
+ sum += *it;
+ }
+ assert(sum == 1 + 2 + 3 + 4 + 5 + 6 + 7);
+ }
+
+ {
+ // testing concatable constraint
+ static_assert(!ConcatableConstViews<ViewWithNoConstBegin>);
+ static_assert(ConcatableConstViews<ViewWithConstBegin>);
+ static_assert(!ConcatableConstViews<ViewWithNoConstBegin, ViewWithConstBegin>);
+ static_assert(!ConcatableConstViews<ViewWithNoConstBegin, ViewWithConstBegin, SizedViewWithConstBegin>);
+ static_assert(ConcatableConstViews<ViewWithConstBegin, SizedViewWithConstBegin>);
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.concat/iterator/arithmetic.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.concat/iterator/arithmetic.pass.cpp
new file mode 100644
index 0000000000000..401de04bf34aa
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.concat/iterator/arithmetic.pass.cpp
@@ -0,0 +1,655 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// operator-(x, y)
+// operator-(x, default_sentinel )
+// operator-(default_sentinel , x)
+// operator-(x, n)
+// operator+(x, n)
+// operator+=(x, n)
+// operator-=(x, n)
+
+#include <array>
+#include <cassert>
+#include <concepts>
+#include <functional>
+#include <list>
+#include <ranges>
+#include <span>
+#include <vector>
+#include <utility>
+
+#include "../../range_adaptor_types.h"
+
+template <class T, class U>
+concept canPlusEqual = requires(T& t, U& u) { t += u; };
+
+template <class T, class U>
+concept canMinusEqual = requires(T& t, U& u) { t -= u; };
+
+template <class Iter>
+struct NotSizedSentinelForIter {
+ using iterator_category = std::forward_iterator_tag;
+ using value_type = std::iterator_traits<Iter>::value_type;
+ using
diff erence_type = std::iterator_traits<Iter>::
diff erence_type;
+
+ Iter ptr;
+
+ NotSizedSentinelForIter() = default;
+ NotSizedSentinelForIter(const Iter& p) : ptr(p) {}
+ NotSizedSentinelForIter(const NotSizedSentinelForIter& other) : ptr(other.ptr) {}
+
+ value_type& operator*() const { return *ptr; }
+
+ NotSizedSentinelForIter& operator++() { return *++ptr; }
+ NotSizedSentinelForIter operator++(int) {
+ auto tmp = *this;
+ ++*this;
+ return tmp;
+ }
+
+ friend bool operator==(const NotSizedSentinelForIter& a, const NotSizedSentinelForIter& b) = default;
+};
+
+template <class T>
+struct NotSizedViewWithSizedSentinel;
+
+template <class Iter>
+struct SizedSentinelForIter {
+ using iterator_category = std::forward_iterator_tag;
+ using value_type = std::iterator_traits<Iter>::value_type;
+ using
diff erence_type = std::iterator_traits<Iter>::
diff erence_type;
+
+ Iter ptr;
+ Iter e;
+
+ constexpr SizedSentinelForIter() = default;
+ constexpr SizedSentinelForIter(const Iter& ptr_, const Iter& e_ = nullptr) : ptr(ptr_), e(e_) {}
+ constexpr SizedSentinelForIter(const SizedSentinelForIter& other) : ptr(other.ptr), e(other.e) {}
+
+ constexpr value_type& operator*() const { return *ptr; }
+
+ constexpr SizedSentinelForIter& operator++() {
+ ++ptr;
+ return *this;
+ }
+ constexpr SizedSentinelForIter operator++(int) {
+ auto tmp = *this;
+ ++*this;
+ return tmp;
+ }
+
+ friend constexpr decltype(auto) operator-(const SizedSentinelForIter& a, const SizedSentinelForIter& b) {
+ return a.ptr - b.ptr;
+ }
+
+ friend constexpr bool operator==(const SizedSentinelForIter& a, const SizedSentinelForIter& b) = default;
+};
+
+template <class T>
+struct SizedViewWithoutSizedSentinel : std::ranges::view_base {
+ T* b_;
+ std::size_t sz;
+
+ template <std::size_t N>
+ constexpr SizedViewWithoutSizedSentinel(T (&b)[N]) : b_(b), sz(N) {}
+ constexpr SizedViewWithoutSizedSentinel(T* b, std::size_t N) : b_(b), sz(N) {}
+
+ constexpr NotSizedSentinelForIter<T*> begin() const { return NotSizedSentinelForIter<T*>{b_}; }
+ constexpr NotSizedSentinelForIter<T*> end() const { return NotSizedSentinelForIter<T*>{b_ + sz}; }
+
+ constexpr std::size_t size() { return sz; }
+};
+
+template <class T>
+struct NotSizedViewWithSizedSentinel : std::ranges::view_base {
+ T* b_;
+ std::size_t sz;
+
+ NotSizedViewWithSizedSentinel() = default;
+
+ template <std::size_t N>
+ constexpr NotSizedViewWithSizedSentinel(T (&b)[N]) : b_(b), sz(N) {}
+ constexpr NotSizedViewWithSizedSentinel(T* b, std::size_t N) : b_(b), sz(N) {}
+
+ constexpr SizedSentinelForIter<T*> begin() const { return SizedSentinelForIter<T*>{b_, b_ + sz}; }
+ constexpr SizedSentinelForIter<T*> end() const { return SizedSentinelForIter<T*>{b_ + sz, b_ + sz}; }
+};
+
+static_assert(std::forward_iterator<NotSizedSentinelForIter<int*>>);
+static_assert(std::sized_sentinel_for<std::ranges::sentinel_t<NotSizedViewWithSizedSentinel<int>>,
+ std::ranges::iterator_t<NotSizedViewWithSizedSentinel<int>>>);
+
+template <class... Views>
+concept MinusOperatorWellFormedForDefaultSentinel =
+ requires(std::ranges::concat_view<Views...> cv) { (cv.begin() - std::default_sentinel_t{}); };
+
+constexpr bool test() {
+ int buffer1[5] = {1, 2, 3, 4, 5};
+ int buffer2[9] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
+ int buffer3[4] = {4, 5, 6, 7};
+
+ SimpleCommonRandomAccessSized a{buffer1};
+ SimpleCommonRandomAccessSized b{buffer2};
+ SimpleCommonRandomAccessSized c{buffer3};
+
+ {
+ // operator+(x, n) and operator+=
+ std::ranges::concat_view v(a, b);
+ auto it1 = v.begin();
+
+ auto it2 = it1 + 3;
+ auto x2 = *it2;
+ it2 = it2 + (-2);
+ auto x2_1 = *it2;
+ assert(x2 == buffer1[3]);
+ assert(x2_1 == buffer1[1]);
+
+ auto it3 = 3 + it1;
+ auto x3 = *it3;
+ it3 = (-2) + it3;
+ auto x3_1 = *it3;
+ assert(x3 == buffer1[3]);
+ assert(x3_1 == buffer1[1]);
+
+ it1 += 3;
+ auto x1 = *it1;
+ it1 += (-2);
+ auto x1_1 = *it1;
+ assert(x1 == buffer1[3]);
+ assert(x1_1 == buffer1[1]);
+
+ // reaches the end of one range so it should jump to the beginning of next range
+ auto it = v.begin() + 5;
+ assert(*it == buffer2[0]);
+
+ using Iter = decltype(it1);
+ static_assert(canPlusEqual<Iter, std::intptr_t>);
+ }
+
+ {
+ // operator+(x, n) and operator+=
+ // has empty view
+ std::array<int, 0> e{};
+ std::ranges::concat_view v(a, e, b);
+ auto it1 = v.begin();
+
+ auto it2 = it1 + 3;
+ auto x2 = *it2;
+ it2 = it2 + (-2);
+ auto x2_1 = *it2;
+ assert(x2 == buffer1[3]);
+ assert(x2_1 == buffer1[1]);
+
+ auto it3 = 3 + it1;
+ auto x3 = *it3;
+ it3 = (-2) + it3;
+ auto x3_1 = *it3;
+ assert(x3 == buffer1[3]);
+ assert(x3_1 == buffer1[1]);
+
+ it1 += 3;
+ auto x1 = *it1;
+ it1 += (-2);
+ auto x1_1 = *it1;
+ assert(x1 == buffer1[3]);
+ assert(x1_1 == buffer1[1]);
+
+ // jumps over to next range but next range is empty, so it should go futher
+ auto it = v.begin() + 5;
+ assert(*it == buffer2[0]);
+
+ using Iter = decltype(it1);
+ static_assert(canPlusEqual<Iter, std::intptr_t>);
+ }
+
+ {
+ // operator+(x, n) and operator+=
+ // has more than 2 ranges
+ std::array<int, 0> e{};
+ std::ranges::concat_view v(a, e, b, c);
+ auto it1 = v.begin();
+
+ auto it2 = it1 + 3;
+ auto x2 = *it2;
+ it2 = it2 + (-2);
+ auto x2_1 = *it2;
+ assert(x2 == buffer1[3]);
+ assert(x2_1 == buffer1[1]);
+
+ auto it3 = 3 + it1;
+ auto x3 = *it3;
+ it3 = (-2) + it3;
+ auto x3_1 = *it3;
+ assert(x3 == buffer1[3]);
+ assert(x3_1 == buffer1[1]);
+
+ it1 += 3;
+ auto x1 = *it1;
+ it1 += (-2);
+ auto x1_1 = *it1;
+ assert(x1 == buffer1[3]);
+ assert(x1_1 == buffer1[1]);
+ // jumps over to next range but next range is empty, so it should go futher
+ auto it = v.begin() + 5;
+ assert(*it == buffer2[0]);
+
+ // jumps big enough to skip several number of ranges
+ it = v.begin() + 14;
+ auto x = *it;
+ it = it + (-3);
+ auto x_1 = *it;
+ assert(x == buffer3[0]);
+ assert(x_1 == buffer2[6]);
+
+ using Iter = decltype(it1);
+ static_assert(canPlusEqual<Iter, std::intptr_t>);
+ }
+
+ {
+ // operator-(x, n) and operator-=
+ std::ranges::concat_view v(a, b);
+ auto it1 = v.end();
+
+ auto it2 = it1 - 3;
+ auto x2 = *it2;
+ it2 = it2 - (-1);
+ auto x2_1 = *it2;
+ assert(x2 == buffer2[6]);
+ assert(x2_1 == buffer2[7]);
+
+ it1 -= 2;
+ assert(it1 == it2);
+ auto x1 = *it1;
+ it1 -= (-1);
+ auto x1_1 = *it1;
+ assert(x1 == buffer2[7]);
+ assert(x1_1 == buffer2[8]);
+
+ // move back to one past the begining,
+ // so it should point to the elements of the previous range
+
+ auto it = v.end() - 12;
+ auto x = *it;
+ it -= (-1);
+ auto x_1 = *it;
+ assert(x == buffer1[2]);
+ assert(x_1 == buffer1[3]);
+
+ using Iter = decltype(it1);
+ static_assert(canMinusEqual<Iter, std::intptr_t>);
+ }
+
+ {
+ // operator-(x, n) and operator-=
+ // has empty view
+ std::array<int, 0> e{};
+ std::ranges::concat_view v(a, e, b);
+ auto it1 = v.end();
+
+ auto it2 = it1 - 3;
+ auto x2 = *it2;
+ it2 = it2 - (-1);
+ auto x2_1 = *it2;
+ assert(x2 == buffer2[6]);
+ assert(x2_1 == buffer2[7]);
+
+ it1 -= 2;
+ assert(it1 == it2);
+ auto x1 = *it1;
+ it1 -= (-1);
+ auto x1_1 = *it1;
+ assert(x1 == buffer2[7]);
+ assert(x1_1 == buffer2[8]);
+
+ // move back to an empty range
+ // so it should skip empty range point to the elements of the previous range
+
+ auto it = v.end() - 12;
+ auto x = *it;
+ it -= (-1);
+ auto x_1 = *it;
+ assert(x == buffer1[2]);
+ assert(x_1 == buffer1[3]);
+
+ using Iter = decltype(it1);
+ static_assert(canMinusEqual<Iter, std::intptr_t>);
+ }
+
+ {
+ // operator-(x, n) and operator-=
+ // has more than 2 views
+ std::array<int, 0> e{};
+ std::ranges::concat_view v(a, e, b, c);
+ auto it1 = v.end();
+
+ auto it2 = it1 - 3;
+ auto x2 = *it2;
+ it2 -= (-1);
+ auto x2_1 = *it2;
+ assert(x2 == buffer3[1]);
+ assert(x2_1 == buffer3[2]);
+
+ it1 -= 2;
+ assert(it1 == it2);
+ auto x1 = *it1;
+ it1 -= (-1);
+ auto x1_1 = *it1;
+ assert(x1 == buffer3[2]);
+ assert(x1_1 == buffer3[3]);
+
+ // move back to multiple ranges
+
+ auto it = v.end() - 16;
+ auto x = *it;
+ it = it - (-1);
+ auto x_1 = *it;
+ it -= (-1);
+ auto x_2 = *it;
+ assert(x == buffer1[2]);
+ assert(x_1 == buffer1[3]);
+ assert(x_2 == buffer1[4]);
+
+ using Iter = decltype(it1);
+ static_assert(canMinusEqual<Iter, std::intptr_t>);
+ }
+
+ {
+ // operator-(x, y)
+ // x and y are in
diff erent ranges
+ // underlying ranges are the same
+ // x'index < y's index
+ std::ranges::concat_view v(a, b);
+ assert((v.end() - v.begin()) == 14);
+
+ // x'index < y's index
+ auto it1 = v.begin() + 2;
+ auto it2 = v.end() - 1;
+ assert((it1 - it2) == -11);
+
+ // x'index > y's index
+ assert((it2 - it1) == 11);
+
+ // x'index == y's index
+ it1 = it1 + 11;
+ assert((it2 - it1) == 0);
+ }
+
+ {
+ // opeartor-(x,y)
+ // x and y are in
diff erent ranges
+ // underlying ranges are
diff erent types
+ std::array<int, 3> arr_a{1, 2, 3};
+ std::vector<int> arr_b{4, 5, 6};
+ std::ranges::concat_view v(arr_a, arr_b);
+
+ // x'index < y's index
+ auto it1 = v.begin() + 1;
+ auto it2 = v.end() - 1;
+ assert(*it1 == 2);
+ assert(*it2 == 6);
+ assert((it1 - it2) == -4);
+
+ // x'index > y's index
+ assert((it2 - it1) == 4);
+
+ // x'index == y's index
+ it1 = it1 + 4;
+ assert((it2 - it1) == 0);
+ }
+
+ {
+ // opeartor-(x,y)
+ // x and y are in
diff erent ranges
+ // underlying ranges are
diff erent types, and there are empty ranges in the middle
+ std::array<int, 0> e_1{};
+ std::array<int, 0> e_2{};
+ std::vector<int> arr_b{4, 5, 6};
+ std::ranges::concat_view v(a, e_1, e_2, arr_b);
+
+ // x'index < y's index
+ auto it1 = v.begin() + 1;
+ auto it2 = v.end() - 1;
+ assert(*it1 == 2);
+ assert(*it2 == 6);
+ assert((it1 - it2) == -6);
+
+ // x'index > y's index
+ assert((it2 - it1) == 6);
+
+ // x'index == y's index
+ it1 = it1 + 6;
+ assert((it2 - it1) == 0);
+ }
+
+ {
+ // operator-(default_sentinel , x)
+ std::array<int, 2> array1{0, 1};
+ std::array<int, 2> array2{2, 3};
+ std::array<int, 2> array3{4, 5};
+ std::ranges::concat_view view(std::views::all(array1), std::views::all(array2), std::views::all(array3));
+ auto it1 = view.begin();
+ auto res = std::default_sentinel_t{} - it1;
+ assert(res == 6);
+ }
+
+ {
+ // operator-(default_sentinel , x) with empty ranges
+ std::array<int, 2> array1{0, 1};
+ std::array<int, 0> array2;
+ std::array<int, 2> array3{2, 3};
+ std::ranges::concat_view view(std::views::all(array1), std::views::all(array2), std::views::all(array3));
+ auto it1 = view.begin();
+ auto res = std::default_sentinel_t{} - it1;
+ assert(res == 4);
+ }
+
+ {
+ // operator-(default_sentinel , x) with
diff erent types
+ std::array<int, 2> array1{0, 1};
+ std::vector<int> array2{2, 3};
+ std::array<int, 0> array3{};
+ std::ranges::concat_view view(a, std::views::all(array1), std::views::all(array2), std::views::all(array3));
+ auto it1 = view.begin();
+ auto res = std::default_sentinel_t{} - it1;
+ assert(res == 9);
+ }
+
+ {
+ // operator-(x, default_sentinel)
+ std::array<int, 2> array1{0, 1};
+ std::array<int, 2> array2{2, 3};
+ std::array<int, 2> array3{4, 5};
+ std::ranges::concat_view view(std::views::all(array1), std::views::all(array2), std::views::all(array3));
+ auto it1 = view.begin();
+ auto res = it1 - std::default_sentinel_t{};
+ assert(res == -6);
+ }
+
+ {
+ // operator-(x, default_sentinel) with empty ranges
+ std::array<int, 2> array1{0, 1};
+ std::array<int, 0> array2{};
+ std::array<int, 2> array3{4, 5};
+ std::ranges::concat_view view(std::views::all(array1), std::views::all(array2), std::views::all(array3));
+ auto it1 = view.begin();
+ auto res = it1 - std::default_sentinel_t{};
+ assert(res == -4);
+ }
+
+ {
+ // operator-(x, default_sentinel) with
diff erent types
+ std::array<int, 2> array1{0, 1};
+ std::array<int, 0> array2{};
+ std::vector<int> array3{4, 5};
+ std::ranges::concat_view view(std::views::all(array1), std::views::all(array2), std::views::all(array3));
+ auto it1 = view.begin();
+ auto res = it1 - std::default_sentinel_t{};
+ assert(res == -4);
+ }
+
+ {
+ // operator-(x, default_sentinel)
+ // testing constraints
+
+ // sized_sentinel_for fails
+ static_assert(!MinusOperatorWellFormedForDefaultSentinel<SizedViewWithoutSizedSentinel<int>>);
+ static_assert(!MinusOperatorWellFormedForDefaultSentinel<SizedViewWithoutSizedSentinel<int>,
+ NonSimpleCommonRandomAccessSized>);
+ static_assert(
+ MinusOperatorWellFormedForDefaultSentinel<SimpleCommonRandomAccessSized, NonSimpleCommonRandomAccessSized>);
+
+ // let Fs be the pack containing all views but the first one
+ // sized_sentinel_for succeeds and sized_range<Fs...> succeeds
+ // first range does not have size() but satisfies sized_sentinel_for
+ int arr_a[3] = {0, 1, 2};
+ int arr_b[3] = {4, 5, 6};
+ static_assert(
+ MinusOperatorWellFormedForDefaultSentinel<NotSizedViewWithSizedSentinel<int>, SimpleCommonRandomAccessSized>);
+ std::ranges::concat_view cv{NotSizedViewWithSizedSentinel<int>{arr_a}, SimpleCommonRandomAccessSized{arr_b}};
+ auto it = cv.begin();
+ it++;
+ assert((it - std::default_sentinel_t{}) == -5);
+ assert((std::default_sentinel_t{} - it) == 5);
+
+ // sized_sentinel_for succeeds but sized_range<Fs...> fails
+ static_assert(!MinusOperatorWellFormedForDefaultSentinel<NotSizedViewWithSizedSentinel<int>, InputCommonView>);
+ static_assert(!MinusOperatorWellFormedForDefaultSentinel<SimpleCommonRandomAccessSized, InputCommonView>);
+ }
+
+ {
+ // One of the ranges is not random access
+ std::ranges::concat_view v(a, b, ForwardSizedView{buffer1});
+ using Iter = decltype(v.begin());
+ static_assert(!std::invocable<std::plus<>, Iter, std::intptr_t>);
+ static_assert(!std::invocable<std::plus<>, std::intptr_t, Iter>);
+ static_assert(!canPlusEqual<Iter, std::intptr_t>);
+ static_assert(!std::invocable<std::minus<>, Iter, std::intptr_t>);
+ static_assert(!std::invocable<std::minus<>, Iter, Iter>);
+ static_assert(!canMinusEqual<Iter, std::intptr_t>);
+ }
+
+ {
+ // One of the ranges is not random access
+ std::ranges::concat_view v(a, b, InputCommonView{buffer1});
+ using Iter = decltype(v.begin());
+ static_assert(!std::invocable<std::minus<>, Iter, Iter>);
+ }
+
+ {
+ // random access check
+ std::array<int, 4> a1{1, 2, 3, 4};
+ std::array<int, 2> b1{5, 6};
+ std::span<const int> s1{a1};
+ std::span<const int> s2{b1};
+
+ // All random-access & all non-last are common => random access iterator
+ {
+ auto v = std::views::concat(s1, s2); // both spans are RA & common; non-last (s1) is common
+ using Iter = decltype(v.begin());
+ using CIter = decltype(std::as_const(v).begin());
+ static_assert(std::random_access_iterator<Iter>);
+ static_assert(std::random_access_iterator<CIter>);
+ }
+
+ // Others are common and last is be non-common => still random access
+ {
+ auto last_non_common = std::views::counted(a1.data(), static_cast<std::ptr
diff _t>(a1.size()));
+ auto v = std::views::concat(s2, last_non_common); // s2 is common; last is allowed to be non-common
+ using Iter = decltype(v.begin());
+ using CIter = decltype(std::as_const(v).begin());
+ static_assert(std::random_access_iterator<Iter>);
+ static_assert(std::random_access_iterator<CIter>);
+ }
+
+ // a non-last range is non-common => NOT random access
+ {
+ int buffer[3] = {1, 2, 3};
+ auto v = std::views::concat(SimpleNonCommon{buffer}, s2);
+ using Iter = decltype(v.begin());
+ using CIter = decltype(std::as_const(v).begin());
+ static_assert(!std::random_access_iterator<Iter>);
+ static_assert(!std::random_access_iterator<CIter>);
+ }
+
+ // one underlying range is not random access => NOT random access
+ {
+ std::list<int> ls{1, 2, 3};
+ auto v = std::views::concat(ls, s2);
+ using Iter = decltype(v.begin());
+ using CIter = decltype(std::as_const(v).begin());
+ static_assert(!std::random_access_iterator<Iter>);
+ static_assert(!std::random_access_iterator<CIter>);
+ }
+ }
+
+ {
+ // operator+(x, n) and operator+(n, x), where n is negative
+ std::array<int, 4> arr_a{1, 2, 3, 4};
+ std::array<int, 3> arr_b{5, 6, 7};
+ std::span<const int> s1{arr_a};
+ std::span<const int> s2{arr_b};
+ auto v = std::views::concat(s1, s2);
+
+ auto i = v.begin();
+ std::ranges::advance(i, arr_a.size());
+
+ auto j = i;
+ auto j_1 = j + (-1);
+ assert(*j_1 == arr_a.back());
+ auto j_3 = j + (-3);
+ assert(*j_3 == arr_a[1]);
+
+ // n + x (negative)
+ auto k = (-1) + j;
+ assert(*k == arr_a.back());
+
+ // const-iterator
+ auto ci = std::as_const(v).begin();
+ std::ranges::advance(ci, arr_a.size());
+ auto cj = ci;
+ auto cj_2 = cj + (-2);
+ assert(*cj_2 == arr_a[2]);
+ auto cjn = (-1) + cj;
+ assert(*cjn == arr_a.back());
+ }
+
+ {
+ // operator-(x, n), where n is negative
+ std::array<int, 4> arr_a{1, 2, 3, 4};
+ std::array<int, 3> arr_b{5, 6, 7};
+ std::span<const int> s1{arr_a};
+ std::span<const int> s2{arr_b};
+ auto v = std::views::concat(s1, s2);
+
+ auto i = v.begin();
+
+ auto j = i;
+ auto j_1 = j - (-1);
+ assert(*j_1 == arr_a[1]);
+ auto j_3 = j - (-3);
+ assert(*j_3 == arr_a.back());
+
+ // const-iterator
+ auto ci = std::as_const(v).begin();
+ auto cj = ci;
+ auto cj_2 = cj - (-2);
+ assert(*cj_2 == arr_a[2]);
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.concat/iterator/compare.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.concat/iterator/compare.pass.cpp
new file mode 100644
index 0000000000000..ba61923ee955f
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.concat/iterator/compare.pass.cpp
@@ -0,0 +1,236 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// operator==(x,y)
+// operator==(x, sentinel)
+// operator<(x, y)
+// operator<=(x, y)
+// operator>=(x, y)
+// operator>(x, y)
+// operator<=>(x, y)
+
+#include <array>
+#include <cassert>
+#include <ranges>
+#include <vector>
+
+#include "../../range_adaptor_types.h"
+
+template <class Iter>
+concept canCompareEqual = requires(Iter a, Iter b) { a == b; };
+
+template <class Iter>
+concept canCompareEqualWithDefaultSentinel = requires(Iter a, std::default_sentinel_t b) { a == b; };
+
+template <class Iter>
+concept canCompareLessthan = requires(Iter a, Iter b) { a < b; };
+
+template <class Iter>
+concept canCompareLessthanOrEqual = requires(Iter a, Iter b) { a <= b; };
+
+template <class Iter>
+concept canCompareGreaterThan = requires(Iter a, Iter b) { a > b; };
+
+template <class Iter>
+concept canCompareGreaterThanOrEqual = requires(Iter a, Iter b) { a >= b; };
+
+template <class Iter>
+concept canCompareThreeWay = requires(Iter a, Iter b) { a <=> b; };
+
+constexpr bool test() {
+ {
+ // test with one view
+ std::array<int, 5> array{0, 1, 2, 3, 4};
+ std::ranges::concat_view view((array));
+ assert(!(view.begin() == view.end()));
+ assert(view.begin() != view.end());
+ decltype(auto) it1 = view.begin();
+ decltype(auto) it2 = view.begin();
+ std::same_as<bool> decltype(auto) result = (it1 == it2);
+ assert(result);
+
+ ++it1;
+ assert(!(it1 == it2));
+ assert(!(it2 == it1));
+ }
+
+ {
+ // test with more than one view
+ std::array<int, 3> array1{0, 1, 2};
+ std::vector<int> array2{0, 1, 2};
+ std::ranges::concat_view view(std::views::all(array1), std::views::all(array2));
+ decltype(auto) it1 = view.begin();
+ decltype(auto) it2 = view.begin();
+ std::same_as<bool> decltype(auto) result = (it1 == it2);
+ assert(result);
+
+ ++it2;
+ ++it2;
+ assert(!(it1 == it2));
+ assert(!(it2 == it1));
+ assert(it2 != it1);
+ assert(it1 != it2);
+ ++it2;
+ assert(*it1 == *it2);
+ assert(*it2 == *it1);
+ assert(!(*it1 != *it2));
+ assert(!(*it2 != *it1));
+ }
+
+ {
+ // test with more than one view and iterators are in
diff erent range
+ std::array<int, 3> array1{0, 1, 2};
+ std::vector<int> array2{4, 5, 6};
+ std::ranges::concat_view view(std::views::all(array1), std::views::all(array2));
+ decltype(auto) it1 = view.begin();
+ decltype(auto) it2 = view.begin() + 3;
+
+ assert(it1 != it2);
+ assert(it2 != it1);
+ assert(!(it1 == it2));
+ assert(!(it2 == it1));
+ assert(*it1 == 0);
+ assert(*it2 == 4);
+ it1++;
+ it2++;
+ assert(*it1 == 1);
+ assert(*it2 == 5);
+ }
+
+ {
+ // operator==(x, sentinel)
+ std::array<int, 2> array1{1, 2};
+ std::vector<int> array2{3, 4};
+ std::ranges::concat_view v(std::views::all(array1), std::views::all(array2));
+
+ auto it = v.begin();
+ assert(!(it == std::default_sentinel_t{}));
+ assert(!(std::default_sentinel_t{} == it));
+ assert(it != std::default_sentinel_t{});
+ assert(std::default_sentinel_t{} != it);
+
+ it++;
+ it++;
+ it++;
+ it++;
+ assert(it == std::default_sentinel_t{});
+ assert(std::default_sentinel_t{} == it);
+ assert(!(it != std::default_sentinel_t{}));
+ assert(!(std::default_sentinel_t{} != it));
+
+ // const-iterator
+ const auto& cv = v;
+ auto cit = cv.begin();
+ ++cit;
+ ++cit;
+ ++cit;
+ ++cit;
+ assert(cit == std::default_sentinel_t{});
+ assert(std::default_sentinel_t{} == cit);
+ assert(!(cit != std::default_sentinel_t{}));
+ assert(!(std::default_sentinel_t{} != cit));
+ }
+
+ {
+ // operator <, <=, >, >=
+ std::array<int, 4> arr_a{1, 2, 3, 4};
+ std::vector<int> arr_b{5, 6, 7};
+ auto v = std::views::concat(arr_a, arr_b);
+ auto i = v.begin();
+ auto j = v.begin();
+ std::ranges::advance(j, arr_a.size());
+
+ assert(i < j);
+ assert(i <= j);
+ assert(!(i > j));
+ assert(!(i >= j));
+ assert((i <=> j) == std::strong_ordering::less);
+ assert((i <=> i) == std::strong_ordering::equal);
+ assert((j <=> i) == std::strong_ordering::greater);
+
+ auto k = j;
+ assert(!(j < k));
+ assert(j <= k);
+ assert(!(j > k));
+ assert(j >= k);
+ auto ord2 = (j <=> k);
+ assert(ord2 == 0);
+
+ // const-iterator
+ const auto& cv = v;
+ auto ci = cv.begin();
+ auto cj = cv.begin();
+ std::ranges::advance(cj, arr_a.size());
+ assert(ci < cj);
+ assert((ci <=> cj) < 0);
+ }
+
+ {
+ // operator <, <=, >, >=
+ // two pointers point to elements in the same range
+ std::array<int, 4> arr_a{1, 2, 3, 4};
+ std::vector<int> arr_b{5, 6, 7};
+ auto v = std::views::concat(arr_a, arr_b);
+ auto i = v.begin();
+ auto j = v.begin() + 2;
+
+ assert(i < j);
+ assert(i <= j);
+ assert(!(i > j));
+ assert(!(i >= j));
+ assert((i <=> j) == std::strong_ordering::less);
+ assert((i <=> i) == std::strong_ordering::equal);
+ assert((j <=> i) == std::strong_ordering::greater);
+
+ auto k = j;
+ assert(!(j < k));
+ assert(j <= k);
+ assert(!(j > k));
+ assert(j >= k);
+ auto ord2 = (j <=> k);
+ assert(ord2 == 0);
+
+ // const-iterator
+ const auto& cv = v;
+ auto ci = cv.begin();
+ auto cj = cv.begin();
+ std::ranges::advance(cj, arr_a.size());
+ assert(ci < cj);
+ assert((ci <=> cj) < 0);
+ }
+
+ {
+ // operator ==,<, <=, >, >=, <=>
+ // should not be invocable on non-random access range
+ static_assert(!canCompareEqual<std::ranges::concat_view<ForwardSizedView>>);
+ static_assert(!canCompareEqualWithDefaultSentinel<std::ranges::concat_view<ForwardSizedView>>);
+ static_assert(!canCompareLessthan<std::ranges::concat_view<ForwardSizedView>>);
+ static_assert(!canCompareLessthanOrEqual<std::ranges::concat_view<ForwardSizedView>>);
+ static_assert(!canCompareGreaterThan<std::ranges::concat_view<ForwardSizedView>>);
+ static_assert(!canCompareGreaterThanOrEqual<std::ranges::concat_view<ForwardSizedView>>);
+ static_assert(!canCompareThreeWay<std::ranges::concat_view<ForwardSizedView>>);
+
+ static_assert(!canCompareEqual<std::ranges::concat_view<ForwardSizedView, SizedBidiCommon>>);
+ static_assert(!canCompareEqualWithDefaultSentinel<std::ranges::concat_view<ForwardSizedView, SizedBidiCommon>>);
+ static_assert(!canCompareLessthan<std::ranges::concat_view<ForwardSizedView, SizedBidiCommon>>);
+ static_assert(!canCompareLessthanOrEqual<std::ranges::concat_view<ForwardSizedView, SizedBidiCommon>>);
+ static_assert(!canCompareGreaterThan<std::ranges::concat_view<ForwardSizedView, SizedBidiCommon>>);
+ static_assert(!canCompareGreaterThanOrEqual<std::ranges::concat_view<ForwardSizedView, SizedBidiCommon>>);
+ static_assert(!canCompareThreeWay<std::ranges::concat_view<ForwardSizedView, SizedBidiCommon>>);
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.concat/iterator/ctor.default.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.concat/iterator/ctor.default.pass.cpp
new file mode 100644
index 0000000000000..f27fe3a14076c
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.concat/iterator/ctor.default.pass.cpp
@@ -0,0 +1,49 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: has-fblocks
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20, c++23
+
+// constexpr iterator() = default;
+
+#include <ranges>
+#include <type_traits>
+
+#include "test_macros.h"
+
+struct IntView : std::ranges::view_base {
+ int* b_ = nullptr;
+ int* e_ = nullptr;
+
+ constexpr IntView() = default;
+ constexpr IntView(int* b, int* e) : b_(b), e_(e) {}
+
+ constexpr int* begin() const { return b_; }
+ constexpr int* end() const { return e_; }
+};
+
+static_assert(std::ranges::view<IntView>);
+static_assert(std::ranges::contiguous_range<IntView>);
+
+constexpr bool test() {
+ int buf1[] = {1, 2};
+ int buf2[] = {3, 4};
+
+ std::ranges::concat_view<IntView, IntView> v(IntView{buf1, buf1 + 2}, IntView{buf2, buf2 + 2});
+ using Iter = std::ranges::iterator_t<decltype(v)>;
+ static_assert(std::default_initializable<Iter>);
+ [[maybe_unused]] Iter iter{};
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.concat/iterator/ctor.other.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.concat/iterator/ctor.other.pass.cpp
new file mode 100644
index 0000000000000..34ec64f8af8ec
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.concat/iterator/ctor.other.pass.cpp
@@ -0,0 +1,84 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// constexpr __iterator(__iterator<!_Const> __i)
+// requires _Const && (convertible_to<iterator_t<_Views>, iterator_t<const _Views>> && ...)
+
+#include <cassert>
+#include <ranges>
+#include <utility>
+
+#include "../../range_adaptor_types.h"
+
+using ConstIterIncompatibleView =
+ BasicView<forward_iterator<int*>,
+ forward_iterator<int*>,
+ random_access_iterator<const int*>,
+ random_access_iterator<const int*>>;
+static_assert(!std::convertible_to<std::ranges::iterator_t<ConstIterIncompatibleView>,
+ std::ranges::iterator_t<const ConstIterIncompatibleView>>);
+
+constexpr bool test() {
+ int buffer_1[3] = {1, 2, 3};
+ int buffer_2[3] = {4, 5, 6};
+
+ {
+ std::ranges::concat_view v(NonSimpleCommon{buffer_1}, NonSimpleCommonRandomAccessSized{buffer_2});
+ auto iter1 = v.begin();
+ iter1++;
+ std::ranges::iterator_t<const decltype(v)> iter2 = iter1;
+ assert(iter1 == iter2);
+ assert(*iter1 == 2);
+ assert(*iter2 == 2);
+
+ static_assert(!std::is_same_v<decltype(iter1), decltype(iter2)>);
+
+ // We cannot create a non-const iterator from a const iterator.
+ static_assert(!std::constructible_from<decltype(iter1), decltype(iter2)>);
+ }
+
+ {
+ // iter1 in the second range
+ std::ranges::concat_view v(NonSimpleCommon{buffer_1}, NonSimpleCommonRandomAccessSized{buffer_2});
+ auto iter1 = v.begin();
+ iter1++;
+ iter1++;
+ iter1++;
+ std::ranges::iterator_t<const decltype(v)> iter2 = iter1;
+ assert(iter1 == iter2);
+ assert(*iter1 == 4);
+ assert(*iter2 == 4);
+
+ static_assert(!std::is_same_v<decltype(iter1), decltype(iter2)>);
+
+ // We cannot create a non-const iterator from a const iterator.
+ static_assert(!std::constructible_from<decltype(iter1), decltype(iter2)>);
+ }
+
+ {
+ // underlying non-const to const not convertible
+ std::ranges::concat_view v(ConstIterIncompatibleView{buffer_1}, NonSimpleCommon{buffer_2});
+ auto iter1 = v.begin();
+ auto iter2 = std::as_const(v).begin();
+
+ static_assert(!std::is_same_v<decltype(iter1), decltype(iter2)>);
+ static_assert(!std::constructible_from<decltype(iter1), decltype(iter2)>);
+ static_assert(!std::constructible_from<decltype(iter2), decltype(iter1)>);
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.concat/iterator/decrement.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.concat/iterator/decrement.pass.cpp
new file mode 100644
index 0000000000000..99b35d3412e81
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.concat/iterator/decrement.pass.cpp
@@ -0,0 +1,228 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// constexpr __iterator& operator--()
+
+// constexpr __iterator operator--(int)
+
+#include <array>
+#include <cassert>
+#include <ranges>
+
+#include "test_macros.h"
+#include "../../range_adaptor_types.h"
+
+template <class Iter>
+concept CanDecrement = requires(Iter it) { --it; } || requires(Iter it) { it--; };
+
+struct NonBidi : IntBufferView {
+ using IntBufferView::IntBufferView;
+ using iterator = forward_iterator<int*>;
+ constexpr iterator begin() const { return iterator(buffer_); }
+ constexpr sentinel_wrapper<iterator> end() const { return sentinel_wrapper<iterator>(iterator(buffer_ + size_)); }
+};
+
+constexpr bool test() {
+ std::array<int, 4> a{1, 2, 3, 4};
+ std::array<int, 4> b{5, 6, 7, 8};
+
+ // Test with a single view
+ {
+ std::ranges::concat_view view(a);
+ auto it = std::ranges::next(view.begin(), view.end());
+ assert(it == view.end());
+
+ auto& result = --it;
+ ASSERT_SAME_TYPE(decltype(result)&, decltype(--it));
+ assert(&result == &it);
+ assert(result == view.begin() + 3);
+ }
+
+ // Test with more than one view
+ {
+ std::ranges::concat_view view(a, b);
+ auto it = std::ranges::next(view.begin(), view.end());
+ assert(it == view.end());
+
+ auto& result = --it;
+ assert(&result == &it);
+
+ --it;
+ assert(*it == 7);
+ assert(it == view.begin() + 6);
+ }
+
+ // Test going forward and then backward on the same iterator
+ {
+ std::ranges::concat_view view(a, b);
+ auto it = view.begin();
+ ++it;
+ --it;
+ assert(*it == a[0]);
+ ++it;
+ ++it;
+ --it;
+ assert(*it == a[1]);
+ ++it;
+ ++it;
+ --it;
+ assert(*it == a[2]);
+ ++it;
+ ++it;
+ --it;
+ assert(*it == a[3]);
+ }
+
+ // Test post-decrement
+ {
+ std::ranges::concat_view view(a, b);
+ auto it = std::ranges::next(view.begin(), view.end());
+ assert(it == view.end()); // test the test
+ auto result = it--;
+ ASSERT_SAME_TYPE(decltype(result), decltype(it--));
+ assert(result == view.end());
+ assert(it == (result - 1));
+ }
+
+ // bidirectional
+ {
+ int buffer[2] = {1, 2};
+
+ std::ranges::concat_view v(BidiCommonView{buffer}, std::views::iota(0, 5));
+ auto it = v.begin();
+ using Iter = decltype(it);
+
+ ++it;
+ ++it;
+
+ static_assert(std::is_same_v<decltype(--it), Iter&>);
+ auto& it_ref = --it;
+ assert(&it_ref == &it);
+
+ assert(it == ++v.begin());
+
+ static_assert(std::is_same_v<decltype(it--), Iter>);
+ auto tmp = it--;
+ assert(it == v.begin());
+ assert(tmp == ++v.begin());
+ }
+
+ // non bidirectional
+ {
+ int buffer[3] = {4, 5, 6};
+ std::ranges::concat_view v(a, NonBidi{buffer});
+ using Iter = std::ranges::iterator_t<decltype(v)>;
+ static_assert(!CanDecrement<Iter>);
+ }
+
+ // Cross-boundary decrement: from begin of a later range into the previous range's last element.
+ {
+ auto v = std::views::concat(a, b);
+
+ auto it = v.begin();
+ it += a.size();
+ --it;
+ assert(*it == a.back());
+
+ auto it2 = v.begin();
+ it2 += a.size();
+ auto old = it2--;
+ static_assert(std::is_same_v<decltype(old), decltype(it2)>);
+ assert(*old == b.front());
+ assert(*it2 == a.back());
+ }
+
+ // Cross-boundary with three ranges
+ {
+ std::array<int, 3> c{9, 10, 11};
+ auto v3 = std::views::concat(a, b, c);
+
+ auto it = v3.begin();
+ it += a.size() + b.size();
+ --it;
+ assert(*it == b.back());
+
+ // const-iterator.
+ const auto& cv3 = v3;
+ auto cit = cv3.begin();
+ cit += a.size() + b.size();
+ --cit;
+ assert(*cit == b.back());
+ }
+
+ // Cross-boundary decrement where the previous range is empty.
+ {
+ std::array<int, 0> e{};
+ auto v = std::views::concat(a, e, b);
+
+ auto it = v.begin();
+ it += a.size();
+ --it; // this skips e
+ assert(*it == a.back());
+
+ auto it2 = v.begin();
+ it2 += a.size();
+ auto old = it2--;
+ assert(*old == b.front());
+ assert(*it2 == a.back());
+
+ // const-iterator
+ const auto& cv = v;
+ auto cit = cv.begin();
+ cit += a.size();
+ --cit;
+ assert(*cit == a.back());
+ }
+
+ // multiple empty ranges in the middle
+ {
+ std::array<int, 0> e1{}, e2{};
+ auto v = std::views::concat(a, e1, e2, b);
+
+ auto it = v.begin();
+ it += a.size();
+ --it; // skip e2 and e1
+ assert(*it == a.back());
+ }
+
+ // Different range types
+ {
+ std::span<const int> sa{a};
+ auto sb = std::ranges::subrange{b.begin(), b.end()};
+ std::array<int, 2> c{9, 10};
+
+ auto v = std::views::concat(sa, sb, c);
+
+ auto it = v.begin();
+ std::ranges::advance(it, sa.size());
+ --it;
+ assert(*it == a.back());
+
+ auto it2 = v.begin();
+ std::ranges::advance(it2, sa.size() + sb.size());
+ --it2;
+ assert(*it2 == b.back());
+
+ // const-iterator.
+ const auto& cv = v;
+ auto cit = cv.begin();
+ std::ranges::advance(cit, sa.size() + sb.size());
+ --cit;
+ assert(*cit == b.back());
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.concat/iterator/deref.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.concat/iterator/deref.pass.cpp
new file mode 100644
index 0000000000000..eb61bbf840f4f
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.concat/iterator/deref.pass.cpp
@@ -0,0 +1,145 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// constexpr decltype(auto) operator*() const
+
+// REQUIRES: std-at-least-c++26
+
+#include <array>
+#include <cassert>
+#include <ranges>
+#include <string>
+#include <vector>
+
+#include "test_macros.h"
+#include "test_iterators.h"
+#include "../types.h"
+
+template <class Iter, class ValueType = int, class Sent = sentinel_wrapper<Iter>>
+constexpr void test() {
+ {
+ // test with one view
+ using View = minimal_view<Iter, Sent>;
+ using ConcatView = std::ranges::concat_view<View>;
+ using ConcatIterator = std::ranges::iterator_t<ConcatView>;
+
+ auto make_concat_view = [](auto begin, auto end) {
+ View view{Iter(begin), Sent(Iter(end))};
+ return ConcatView(std::move(view));
+ };
+
+ std::array array{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+ ConcatView view = make_concat_view(array.data(), array.data() + array.size());
+ ConcatIterator iter = view.begin();
+ int& result = *iter;
+ ASSERT_SAME_TYPE(int&, decltype(*iter));
+ assert(&result == array.data());
+ }
+
+ {
+ // test with more than one view
+ std::array<int, 3> array1{0, 1, 2};
+ std::array<int, 3> array2{0, 1, 2};
+ std::ranges::concat_view view(std::views::all(array1), std::views::all(array2));
+ decltype(auto) it1 = view.begin();
+ decltype(auto) it2 = view.begin() + 3;
+
+ ASSERT_SAME_TYPE(int&, decltype(*it1));
+ assert(*it1 == *it2);
+ }
+
+ {
+ // constness
+ constexpr static std::array<int, 3> array1{0, 1, 2};
+ constexpr static std::array<int, 3> array2{0, 1, 2};
+ constexpr static std::ranges::concat_view view(std::views::all(array1), std::views::all(array2));
+ decltype(auto) it1 = view.begin();
+ decltype(auto) it2 = view.begin() + 3;
+
+ ASSERT_SAME_TYPE(const int&, decltype(*it1));
+ assert(*it1 == *it2);
+ }
+}
+
+constexpr bool tests() {
+ test<cpp17_input_iterator<int*>>();
+ test<forward_iterator<int*>>();
+ test<bidirectional_iterator<int*>>();
+ test<random_access_iterator<int*>>();
+ test<contiguous_iterator<int*>>();
+ test<int*>();
+
+ {
+ // test with more than one view of
diff erent types
+ std::vector<int> array1{0, 1, 2};
+ std::array<int, 3> array2{3, 4, 5};
+ std::ranges::concat_view cv(std::views::all(array1), std::views::all(array2));
+ decltype(auto) it1 = cv.begin();
+ decltype(auto) it2 = cv.begin();
+
+ ASSERT_SAME_TYPE(int&, decltype(*it1));
+ it2++;
+ assert(*it1 == 0);
+ assert(*it2 == 1);
+ it2++;
+ it2++;
+ assert(*it2 == 3);
+ }
+
+ {
+ // test concat-reference-t
+ {
+ // const std::string& + std::string_view + const std::string&
+ const std::array<std::string, 2> left = {"L0", "L1"};
+ std::array<std::string_view, 1> mid = {std::string_view{"M0"}};
+ const std::array<std::string, 1> right = {"R0"};
+
+ auto v = std::views::concat(left, mid, right);
+ auto it = v.begin();
+ auto cit = std::as_const(v).begin();
+
+ // Common reference of {const std::string&, std::string_view&, const std::string&} ==> std::string_view
+ static_assert(std::is_same_v<decltype(*it), std::string_view>);
+ static_assert(std::is_same_v<decltype(*cit), std::string_view>);
+
+ assert(*it == "L0");
+ assert(*std::next(it, 1) == "L1");
+ assert(*std::next(it, 2) == "M0");
+ assert(*std::next(it, 3) == "R0");
+ }
+
+ {
+ // std::string& + std::string (prvalue) + const std::string&
+ std::array<std::string, 1> left = {"L"};
+ std::vector<std::string> mid = {"M"};
+ const std::array<std::string, 1> right = {"R"};
+
+ auto mid_prvalue = mid | std::views::transform([](const std::string& s) { return s; });
+
+ auto v = std::views::concat(left, mid_prvalue, right);
+ auto it = v.begin();
+ auto cit = std::as_const(v).begin();
+
+ // Common reference of {std::string&, std::string (prvalue), const std::string&} ==> const std::string
+ static_assert(std::is_same_v<decltype(*it), const std::string>);
+ static_assert(std::is_same_v<decltype(*cit), const std::string>);
+
+ assert(*it == "L");
+ assert(*std::next(it, 1) == "M");
+ assert(*std::next(it, 2) == "R");
+ }
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ tests();
+ static_assert(tests());
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.concat/iterator/increment.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.concat/iterator/increment.pass.cpp
new file mode 100644
index 0000000000000..071d71635950e
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.concat/iterator/increment.pass.cpp
@@ -0,0 +1,206 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// constexpr __iterator& operator++()
+
+// constexpr void operator++(int)
+
+#include <array>
+#include <cassert>
+#include <concepts>
+#include <ranges>
+#include <stddef.h>
+#include <type_traits>
+#include <utility>
+
+#include "test_iterators.h"
+#include "../../range_adaptor_types.h"
+
+struct InputRange {
+ using iterator = cpp17_input_iterator<int*>;
+ int* begin_;
+ int* end_;
+ constexpr InputRange(int* b, int* e) : begin_(b), end_(e) {}
+ constexpr iterator begin() const { return iterator(begin_); }
+ constexpr sentinel_wrapper<iterator> end() const { return sentinel_wrapper<iterator>(iterator(end_)); }
+};
+
+static_assert(!std::ranges::forward_range<InputRange>);
+static_assert(std::ranges::input_range<InputRange>);
+
+constexpr bool test() {
+ std::array<int, 4> a{1, 2, 3, 4};
+ std::array<double, 4> b{1.0, 2.0, 3.0};
+
+ // one view
+ {
+ std::ranges::concat_view view(a);
+ auto it = view.begin();
+ using Iter = decltype(it);
+ static_assert(std::is_same_v<decltype(it++), Iter>);
+ static_assert(std::is_same_v<decltype(++it), Iter&>);
+
+ auto& result = ++it;
+ assert(&result == &it);
+ assert(*result == 2);
+
+ it = view.begin();
+ assert(*it++ == 1);
+ assert(*it == 2);
+ }
+
+ // more than one view
+ {
+ std::ranges::concat_view view(a, b);
+ auto it = view.begin();
+ using Iter = decltype(it);
+ static_assert(std::is_same_v<decltype(it++), Iter>);
+ static_assert(std::is_same_v<decltype(++it), Iter&>);
+ auto& result = ++it;
+ assert(&result == &it);
+ assert(*result == 2);
+
+ it = view.begin();
+ auto old = it++;
+ assert(*old == 1);
+ assert(*it == 2);
+ }
+
+ // more than two views
+ {
+ std::ranges::concat_view view(a, b, std::views::iota(0, 5));
+ auto it = view.begin();
+ using Iter = decltype(it);
+ static_assert(std::is_same_v<decltype(it++), Iter>);
+ static_assert(std::is_same_v<decltype(++it), Iter&>);
+ auto& result = ++it;
+ assert(&result == &it);
+ assert(*result == 2);
+ }
+
+ // input range, no postfix operator++
+ {
+ int buffer[3] = {4, 5, 6};
+ std::ranges::concat_view view(a, InputRange{buffer, buffer + 3});
+ auto it = view.begin();
+ using Iter = decltype(it);
+ static_assert(std::is_same_v<decltype(it++), void>);
+ static_assert(std::is_same_v<decltype(++it), Iter&>);
+ auto& result = ++it;
+ assert(&result == &it);
+ assert(*result == 2);
+ }
+
+ // Increment an iterator multiple times
+ {
+ std::ranges::concat_view view(a);
+
+ auto it = view.begin();
+ assert(*it == a[0]);
+
+ ++it;
+ assert(*it == a[1]);
+ ++it;
+ assert(*it == a[2]);
+ ++it;
+ assert(*it == a[3]);
+
+ it = view.begin();
+ auto old = it++;
+ assert(*old == a[0]);
+ assert(*it == a[1]);
+ }
+
+ // Different underlying range types; ++ crosses from end of first into start of second.
+ {
+ std::span<const int> sa{a};
+ auto sb = std::ranges::subrange{b.data(), b.data() + b.size()};
+ auto v = std::views::concat(sa, sb);
+
+ auto it = v.begin();
+ for (size_t i = 1; i < a.size(); i++) {
+ ++it;
+ }
+ assert(*it == a.back());
+
+ ++it;
+ assert(*it == b.front());
+
+ auto it2 = v.begin();
+ for (size_t i = 1; i < a.size(); i++) {
+ ++it2;
+ }
+ auto old = it2++;
+ assert(*old == a.back());
+ assert(*it2 == b.front());
+
+ // Same with a const-iterator.
+ const auto& cv = v;
+ auto cit = cv.begin();
+ for (size_t i = 1; i < a.size(); i++) {
+ ++cit;
+ }
+ ++cit;
+ assert(*cit == b.front());
+ }
+
+ // ++ crosses into next range when that next range is empty.
+ {
+ std::array<int, 0> e{};
+ auto v = std::views::concat(a, e, b);
+
+ auto it = v.begin();
+ for (size_t i = 1; i < a.size(); i++) {
+ ++it;
+ }
+ ++it; // skip e
+ assert(*it == b.front());
+
+ auto it2 = v.begin();
+ for (size_t i = 1; i < a.size(); i++)
+ ++it2;
+ auto old = it2++;
+ assert(*old == a.back());
+ assert(*it2 == b.front());
+
+ // Const-iterator.
+ const auto& cv = v;
+ auto cit = cv.begin();
+ for (size_t i = 1; i < a.size(); i++)
+ ++cit;
+ ++cit;
+ assert(*cit == b.front());
+ }
+
+ // Multiple consecutive empty ranges are skipped on ++.
+ {
+ std::array<int, 0> e1{}, e2{};
+ auto v = std::views::concat(a, e1, e2, b);
+
+ auto it = v.begin();
+ for (size_t i = 1; i < a.size(); i++) {
+ ++it;
+ }
+
+ ++it; // skip e1 and e2
+ assert(*it == b.front());
+ auto old = it++;
+ assert(*old == b.front());
+ assert(*it == b[1]);
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.concat/iterator/iter_move.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.concat/iterator/iter_move.pass.cpp
new file mode 100644
index 0000000000000..cad6687f8b5fa
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.concat/iterator/iter_move.pass.cpp
@@ -0,0 +1,235 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// friend constexpr decltype(auto) iter_move(const __iterator& __it) noexcept(
+// ((is_nothrow_invocable_v< decltype(ranges::iter_move), const iterator_t<__maybe_const<_Const, _Views>>& > &&
+// is_nothrow_convertible_v< range_rvalue_reference_t<__maybe_const<_Const, _Views>>,
+// __concat_rvalue_reference_t<__maybe_const<_Const, _Views>...> >) &&
+// ...))
+
+// REQUIRES: std-at-least-c++26
+
+#include <array>
+#include <cassert>
+#include <concepts>
+#include <ranges>
+#include <utility>
+
+#include "../types.h"
+
+struct ConvMayThrow {
+ int v;
+ operator int() { return v; }
+};
+
+struct ConvNoThrow {
+ int v;
+ operator int() noexcept { return v; }
+};
+
+template <typename T, bool NoThrow>
+struct ThowingIter {
+ using iterator_concept = std::forward_iterator_tag;
+ using iterator_category = std::forward_iterator_tag;
+ using
diff erence_type = std::ptr
diff _t;
+ using value_type = T;
+
+ T* p = nullptr;
+
+ constexpr T& operator*() const noexcept { return *p; }
+ constexpr ThowingIter& operator++() noexcept {
+ ++p;
+ return *this;
+ }
+ constexpr ThowingIter operator++(int) noexcept {
+ ThowingIter tmp = *this;
+ ++*this;
+ return tmp;
+ }
+ friend constexpr bool operator==(ThowingIter, ThowingIter) = default;
+
+ friend constexpr decltype(auto) iter_move(const ThowingIter& it) noexcept(NoThrow) { return std::move(*it.p); }
+};
+
+struct Range : std::ranges::view_base {
+ using Iterator = forward_iterator<int*>;
+ using Sentinel = sentinel_wrapper<Iterator>;
+ constexpr explicit Range(int* b, int* e) : begin_(b), end_(e) {}
+ constexpr Iterator begin() const { return Iterator(begin_); }
+ constexpr Sentinel end() const { return Sentinel(Iterator(end_)); }
+
+private:
+ int* begin_;
+ int* end_;
+};
+
+template <class Iter, class Sentinel>
+struct MiniView : std::ranges::view_base {
+ Iter b{};
+ Sentinel e{};
+ constexpr MiniView() = default;
+ constexpr MiniView(Iter first, Sentinel last) : b(first), e(last) {}
+ constexpr Iter begin() const noexcept { return b; }
+ constexpr Sentinel end() const noexcept { return e; }
+};
+
+constexpr bool test() {
+ int arr[2] = {1, 2};
+ ConvMayThrow maythrow_buf[2] = {{3}, {4}};
+ ConvNoThrow nothrow_buf[2] = {{5}, {6}};
+ {
+ // All underlying iter_move are noexcept
+ // => concat iter_move has noexcept(true)
+ using Iter_NoThrow = ThowingIter<int, true>;
+ using Sentinel_NoThrow = sentinel_wrapper<Iter_NoThrow>;
+ using View_NoThrow = MiniView<Iter_NoThrow, Sentinel_NoThrow>;
+ View_NoThrow v1(Iter_NoThrow(arr), Sentinel_NoThrow(Iter_NoThrow(arr + 1)));
+ View_NoThrow v2(Iter_NoThrow(arr), Sentinel_NoThrow(Iter_NoThrow(arr + 1)));
+
+ auto cv = std::views::concat(v1, v2);
+ using Iter = decltype(cv.begin());
+ using CIter = decltype(std::as_const(cv).begin());
+
+ static_assert(noexcept(std::ranges::iter_move(std::declval<Iter>())));
+ static_assert(noexcept(std::ranges::iter_move(std::declval<CIter>())));
+
+ auto it = cv.begin();
+ std::same_as<int&&> decltype(auto) x = std::ranges::iter_move(it);
+ assert(x == 1);
+ }
+
+ {
+ // All underlying iter_move are noexcept
+ // underlying ranges have
diff erent
+ // => concat iter_move has noexcept(true)
+ using Iter_NoThrow = ThowingIter<int, true>;
+ using Sentinel_NoThrow = sentinel_wrapper<Iter_NoThrow>;
+ using View_NoThrow = MiniView<Iter_NoThrow, Sentinel_NoThrow>;
+ View_NoThrow v1(Iter_NoThrow(arr), Sentinel_NoThrow(Iter_NoThrow(arr + 1)));
+ View_NoThrow v2(Iter_NoThrow(arr), Sentinel_NoThrow(Iter_NoThrow(arr + 1)));
+
+ auto cv = std::views::concat(v1, v2);
+ using Iter = decltype(cv.begin());
+ using CIter = decltype(std::as_const(cv).begin());
+
+ static_assert(noexcept(std::ranges::iter_move(std::declval<Iter>())));
+ static_assert(noexcept(std::ranges::iter_move(std::declval<CIter>())));
+
+ auto it = cv.begin();
+ std::same_as<int&&> decltype(auto) x = std::ranges::iter_move(it);
+ assert(x == 1);
+ }
+
+ {
+ // One underlying may throw
+ // concat iter_move has noexcept(false)
+ using Iter_NoThrow = ThowingIter<int, true>;
+ using Iter_Throw = ThowingIter<int, false>;
+ using Sentinel_NoThrow = sentinel_wrapper<Iter_NoThrow>;
+ using Sentinel_Throw = sentinel_wrapper<Iter_Throw>;
+ using View_NoThrow = MiniView<Iter_NoThrow, Sentinel_NoThrow>;
+ using View_Throw = MiniView<Iter_Throw, Sentinel_Throw>;
+
+ auto cv = std::views::concat(View_NoThrow{Iter_NoThrow{arr}, Sentinel_NoThrow{Iter_NoThrow{arr + 1}}},
+ View_Throw{Iter_Throw{arr}, Sentinel_Throw{Iter_Throw{arr + 1}}});
+
+ using Iter = decltype(cv.begin());
+ using CIter = decltype(std::as_const(cv).begin());
+
+ static_assert(!noexcept(std::ranges::iter_move(std::declval<Iter>())));
+ static_assert(!noexcept(std::ranges::iter_move(std::declval<CIter>())));
+
+ auto it = cv.begin();
+ std::same_as<int&&> decltype(auto) x = std::ranges::iter_move(it);
+ assert(x == 1);
+ }
+
+ {
+ // one underlying iter_move may throw, convert ConvNoThrow to int has noexcept
+ // => iter_move has noexcept(false)
+ using Iter_NoThrow = ThowingIter<int, true>;
+ using IterConv_NoThrow = ThowingIter<ConvNoThrow, false>;
+ using Sentinel_NoThrow = sentinel_wrapper<Iter_NoThrow>;
+ using SentinelConv_NoThrow = sentinel_wrapper<IterConv_NoThrow>;
+ using View_NoThrow = MiniView<Iter_NoThrow, Sentinel_NoThrow>;
+ using ViewHasConv_NoThrow = MiniView<IterConv_NoThrow, SentinelConv_NoThrow>;
+
+ auto cv = std::views::concat(
+ View_NoThrow{Iter_NoThrow{arr}, Sentinel_NoThrow{Iter_NoThrow{arr + 1}}},
+ ViewHasConv_NoThrow{IterConv_NoThrow{nothrow_buf}, SentinelConv_NoThrow{IterConv_NoThrow{nothrow_buf + 1}}});
+
+ using Iter = decltype(cv.begin());
+ using CIter = decltype(std::as_const(cv).begin());
+
+ static_assert(!noexcept(std::ranges::iter_move(std::declval<Iter>())));
+ static_assert(!noexcept(std::ranges::iter_move(std::declval<CIter>())));
+
+ auto it = cv.begin();
+ std::same_as<int> decltype(auto) x = std::ranges::iter_move(it);
+ assert(x == 1);
+ }
+
+ {
+ // all underlying iter_move has noexcept, and convert ConvNoThrow to int has noexcept
+ // => concat iter_move has noexcept(true)
+ using Iter_NoThrow = ThowingIter<int, true>;
+ using IterConv_NoThrow = ThowingIter<ConvNoThrow, true>;
+ using Sentinel_NoThrow = sentinel_wrapper<Iter_NoThrow>;
+ using SentinelConv_NoThrow = sentinel_wrapper<IterConv_NoThrow>;
+ using View_NoThrow = MiniView<Iter_NoThrow, Sentinel_NoThrow>;
+ using ViewHasConv_NoThrow = MiniView<IterConv_NoThrow, SentinelConv_NoThrow>;
+
+ auto cv = std::views::concat(
+ View_NoThrow{Iter_NoThrow{arr}, Sentinel_NoThrow{Iter_NoThrow{arr + 1}}},
+ ViewHasConv_NoThrow{IterConv_NoThrow{nothrow_buf}, SentinelConv_NoThrow{IterConv_NoThrow{nothrow_buf + 1}}});
+
+ using Iter = decltype(cv.begin());
+ using CIter = decltype(std::as_const(cv).begin());
+
+ static_assert(noexcept(std::ranges::iter_move(std::declval<Iter>())));
+ static_assert(noexcept(std::ranges::iter_move(std::declval<CIter>())));
+
+ auto it = cv.begin();
+ std::same_as<int> decltype(auto) x = std::ranges::iter_move(it);
+ assert(x == 1);
+ }
+
+ {
+ // underlying iter_move has noexcept, but convert ConvMayThrow to int is noexcept(false)
+ // => concat iter_move has noexcept(false)
+ using Iter_NoThrow = ThowingIter<int, true>;
+ using IterConv_MayThrow = ThowingIter<ConvMayThrow, true>;
+ using Sentinel_NoThrow = sentinel_wrapper<Iter_NoThrow>;
+ using SentinelConv_MayThrow = sentinel_wrapper<IterConv_MayThrow>;
+ using View_NoThrow = MiniView<Iter_NoThrow, Sentinel_NoThrow>;
+ using ViewHasConv_MayThrow = MiniView<IterConv_MayThrow, SentinelConv_MayThrow>;
+
+ auto cv = std::views::concat(
+ View_NoThrow{Iter_NoThrow{arr}, Sentinel_NoThrow{Iter_NoThrow{arr + 1}}},
+ ViewHasConv_MayThrow{
+ IterConv_MayThrow{maythrow_buf}, SentinelConv_MayThrow{IterConv_MayThrow{maythrow_buf + 1}}});
+
+ using Iter = decltype(cv.begin());
+ using CIter = decltype(std::as_const(cv).begin());
+
+ static_assert(!noexcept(std::ranges::iter_move(std::declval<Iter>())));
+ static_assert(!noexcept(std::ranges::iter_move(std::declval<CIter>())));
+
+ auto it = cv.begin();
+ std::same_as<int> decltype(auto) x = std::ranges::iter_move(it);
+ assert(x == 1);
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.concat/iterator/iter_swap.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.concat/iterator/iter_swap.pass.cpp
new file mode 100644
index 0000000000000..a647de4fe913f
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.concat/iterator/iter_swap.pass.cpp
@@ -0,0 +1,199 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// friend constexpr void iter_swap(const __iterator& __x, const __iterator& __y)
+// noexcept((noexcept(ranges::swap(*__x, *__y))) &&
+// (noexcept(ranges::iter_swap(std::declval<const iterator_t<__maybe_const<_Const, _Views>>>(),
+// std::declval<const iterator_t<__maybe_const<_Const, _Views>>>())) &&
+// ...))
+
+// REQUIRES: std-at-least-c++26
+
+#include <array>
+#include <cassert>
+#include <iterator>
+#include <ranges>
+#include <utility>
+
+#include "test_iterators.h"
+#include "test_macros.h"
+#include "../types.h"
+#include "../../range_adaptor_types.h"
+
+template <class It>
+concept has_iter_swap = requires(It it) { std::ranges::iter_swap(it, it); };
+
+struct Elem {
+ int v;
+ friend constexpr void swap(Elem& a, Elem& b) noexcept { std::ranges::swap(a.v, b.v); }
+};
+
+template <typename T>
+struct SwapIter {
+ using iterator_concept = std::forward_iterator_tag;
+ using iterator_category = std::forward_iterator_tag;
+ using
diff erence_type = std::ptr
diff _t;
+ using value_type = T;
+
+ T* p = nullptr;
+
+ constexpr T& operator*() const noexcept { return *p; }
+ constexpr SwapIter& operator++() noexcept {
+ ++p;
+ return *this;
+ }
+ constexpr SwapIter operator++(int) noexcept {
+ SwapIter tmp = *this;
+ ++*this;
+ return tmp;
+ }
+ friend constexpr bool operator==(SwapIter, SwapIter) = default;
+
+ friend constexpr void iter_swap(const SwapIter& it1, const SwapIter& it2) { std::ranges::swap(*it1, *it2); }
+};
+
+template <typename T>
+struct SwapIterNoCustom {
+ using iterator_concept = std::forward_iterator_tag;
+ using iterator_category = std::forward_iterator_tag;
+ using
diff erence_type = std::ptr
diff _t;
+ using value_type = T;
+
+ T* p = nullptr;
+
+ constexpr T& operator*() const noexcept { return *p; }
+ constexpr SwapIterNoCustom& operator++() noexcept {
+ ++p;
+ return *this;
+ }
+ constexpr SwapIterNoCustom operator++(int) noexcept {
+ SwapIterNoCustom tmp = *this;
+ ++*this;
+ return tmp;
+ }
+ friend constexpr bool operator==(SwapIterNoCustom, SwapIterNoCustom) = default;
+};
+
+template <typename T>
+struct SwapIterTracked {
+ using iterator_concept = std::forward_iterator_tag;
+ using iterator_category = std::forward_iterator_tag;
+ using
diff erence_type = std::ptr
diff _t;
+ using value_type = T;
+
+ T* p = nullptr;
+ int* iter_swap_called_times = nullptr;
+
+ constexpr T& operator*() const noexcept { return *p; }
+ constexpr SwapIterTracked& operator++() noexcept {
+ ++p;
+ return *this;
+ }
+ constexpr SwapIterTracked operator++(int) noexcept {
+ SwapIterTracked tmp = *this;
+ ++*this;
+ return tmp;
+ }
+ friend constexpr bool operator==(const SwapIterTracked& x, const SwapIterTracked& y) { return x.p == y.p; }
+
+ friend constexpr void iter_swap(const SwapIterTracked& x, const SwapIterTracked& y) {
+ if (x.iter_swap_called_times)
+ ++*x.iter_swap_called_times;
+ std::ranges::swap(*x, *y);
+ }
+};
+
+template <typename Iter, class Sentinel>
+struct MiniView : std::ranges::view_base {
+ Iter b{};
+ Sentinel e{};
+ constexpr MiniView() = default;
+ constexpr MiniView(Iter first, Sentinel last) : b(first), e(last) {}
+ constexpr Iter begin() const noexcept { return b; }
+ constexpr Sentinel end() const noexcept { return e; }
+};
+
+constexpr bool test() {
+ using IteratorA = SwapIter<Elem>;
+ using SentinelA = sentinel_wrapper<IteratorA>;
+ using IteratorB = SwapIterNoCustom<Elem>;
+ using SentinelB = sentinel_wrapper<IteratorB>;
+ using ViewA = MiniView<IteratorA, SentinelA>;
+ using ViewB = MiniView<IteratorB, SentinelB>;
+
+ {
+ Elem a1[2]{{1}, {2}};
+ Elem a2[2]{{3}, {4}};
+
+ ViewA v1{IteratorA(a1), SentinelA(IteratorA(a1 + 2))};
+ ViewB v2{IteratorB(a2), SentinelB(IteratorB(a2 + 2))};
+
+ std::ranges::concat_view cv(v1, v2);
+
+ auto it1 = cv.begin();
+ auto it2 = ++cv.begin();
+ it2++;
+
+ // always false: https://cplusplus.github.io/LWG/lwg-active.html#4489
+ static_assert(noexcept(std::ranges::iter_swap(it1, it2)) == false);
+
+ std::ranges::iter_swap(it1, it2);
+
+ // iter_swap
+ assert(a1[0].v == 3 && a2[0].v == 1);
+ }
+
+ // Test that the underlying iterator's iter_swap specialization is called
+ // when both iterators point to the same underlying range
+ {
+ int iter_swap_call_count = 0;
+ Elem a1[3]{{10}, {20}, {30}};
+ Elem a2[2]{{40}, {50}};
+
+ using TrackedIter = SwapIterTracked<Elem>;
+ using TrackedSentinel = sentinel_wrapper<TrackedIter>;
+ using TrackedView = MiniView<TrackedIter, TrackedSentinel>;
+
+ TrackedView tv{TrackedIter{a1, &iter_swap_call_count}, TrackedSentinel{TrackedIter{a1 + 3, &iter_swap_call_count}}};
+ ViewB vb{IteratorB{a2}, SentinelB{IteratorB{a2 + 2}}};
+
+ std::ranges::concat_view cv(tv, vb);
+
+ auto it1 = cv.begin();
+ auto it2 = ++cv.begin();
+
+ assert(iter_swap_call_count == 0);
+ std::ranges::iter_swap(it1, it2);
+ // The underlying iter_swap specialization should have been called
+ assert(iter_swap_call_count == 1);
+ assert(a1[0].v == 20 && a1[1].v == 10);
+
+ // Call again to verify the count keeps incrementing
+ std::ranges::iter_swap(it1, it2);
+ assert(iter_swap_call_count == 2);
+ assert(a1[0].v == 10 && a1[1].v == 20);
+ }
+
+ // Test that iter_swap requires the underlying iterator to be iter_swappable
+ {
+ using Iterator = int const*;
+ using View = minimal_view<Iterator, Iterator>;
+ using ConcatView = std::ranges::concat_view<View>;
+ using ConcatIterator = std::ranges::iterator_t<ConcatView>;
+ static_assert(!std::indirectly_swappable<Iterator>);
+ static_assert(!has_iter_swap<ConcatIterator>);
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.concat/iterator/member_types.compile.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.concat/iterator/member_types.compile.pass.cpp
new file mode 100644
index 0000000000000..a105641717562
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.concat/iterator/member_types.compile.pass.cpp
@@ -0,0 +1,256 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// test iterator_concept, iterator_category,
diff erence_type, value_type
+
+#include <array>
+#include <forward_list>
+#include <list>
+#include <ranges>
+#include <tuple>
+#include <utility>
+
+#include "test_iterators.h"
+#include "../../range_adaptor_types.h"
+
+template <class T>
+struct ForwardView : std::ranges::view_base {
+ forward_iterator<T*> begin() const;
+ sentinel_wrapper<forward_iterator<T*>> end() const;
+};
+
+template <class T>
+struct InputView : std::ranges::view_base {
+ cpp17_input_iterator<T*> begin() const;
+ sentinel_wrapper<cpp17_input_iterator<T*>> end() const;
+};
+
+template <class T>
+concept HasIterCategory = requires { typename T::iterator_category; };
+
+template <class T>
+struct DiffTypeIter {
+ using iterator_category = std::input_iterator_tag;
+ using value_type = int;
+ using
diff erence_type = T;
+
+ int operator*() const;
+ DiffTypeIter& operator++();
+ void operator++(int);
+ friend constexpr bool operator==(DiffTypeIter, DiffTypeIter) = default;
+};
+
+template <class T>
+struct DiffTypeRange {
+ DiffTypeIter<T> begin() const;
+ DiffTypeIter<T> end() const;
+};
+
+struct Foo {};
+struct Bar {};
+
+struct ConstVeryDifferentRange {
+ int* begin();
+ int* end();
+
+ forward_iterator<double*> begin() const;
+ forward_iterator<double*> end() const;
+};
+
+constexpr bool test() {
+ int buffer[] = {1, 2, 3, 4};
+ {
+ // random_access_iterator_tag
+ std::ranges::concat_view v(buffer, buffer);
+ using Iter = decltype(v.begin());
+
+ static_assert(std::is_same_v<Iter::iterator_concept, std::random_access_iterator_tag>);
+ static_assert(std::is_same_v<Iter::iterator_category, std::random_access_iterator_tag>);
+ static_assert(std::is_same_v<Iter::
diff erence_type, std::ptr
diff _t>);
+ static_assert(std::is_same_v<Iter::value_type, int>);
+ static_assert(HasIterCategory<Iter>);
+ }
+
+ {
+ // 3 views
+ std::ranges::concat_view v(buffer, buffer, buffer);
+ using Iter = decltype(v.begin());
+
+ static_assert(std::is_same_v<Iter::iterator_concept, std::random_access_iterator_tag>);
+ static_assert(std::is_same_v<Iter::iterator_category, std::random_access_iterator_tag>);
+ static_assert(std::is_same_v<Iter::
diff erence_type, std::ptr
diff _t>);
+ static_assert(std::is_same_v<Iter::value_type, int>);
+ static_assert(HasIterCategory<Iter>);
+ }
+
+ {
+ // bidirectional_iterator_tag
+ std::ranges::concat_view v(BidiCommonView{buffer});
+ using Iter = decltype(v.begin());
+
+ static_assert(std::is_same_v<Iter::iterator_concept, std::bidirectional_iterator_tag>);
+ static_assert(std::is_same_v<Iter::iterator_category, std::bidirectional_iterator_tag>);
+ static_assert(std::is_same_v<Iter::
diff erence_type, std::ptr
diff _t>);
+ static_assert(std::is_same_v<Iter::value_type, int>);
+ }
+
+ {
+ // forward_iterator_tag
+ using Iter = std::ranges::iterator_t<std::ranges::concat_view<ForwardView<int>>>;
+
+ static_assert(std::is_same_v<Iter::iterator_concept, std::forward_iterator_tag>);
+ static_assert(std::is_same_v<Iter::iterator_category, std::forward_iterator_tag>);
+ static_assert(std::is_same_v<Iter::
diff erence_type, std::ptr
diff _t>);
+ static_assert(std::is_same_v<Iter::value_type, int>);
+ static_assert(HasIterCategory<Iter>);
+ }
+
+ {
+ // input_iterator_tag
+ using Iter = std::ranges::iterator_t<std::ranges::concat_view<InputView<int>>>;
+
+ static_assert(std::is_same_v<Iter::iterator_concept, std::input_iterator_tag>);
+ static_assert(!HasIterCategory<Iter>);
+ static_assert(std::is_same_v<Iter::
diff erence_type, std::ptr
diff _t>);
+ static_assert(std::is_same_v<Iter::value_type, int>);
+ }
+
+ {
+ //
diff erence_type of single view
+ std::ranges::concat_view v{DiffTypeRange<std::intptr_t>{}};
+ using Iter = decltype(v.begin());
+ static_assert(std::is_same_v<Iter::
diff erence_type, std::intptr_t>);
+ }
+
+ {
+ // value_type of single view
+ std::ranges::concat_view v{DiffTypeRange<std::intptr_t>{}};
+ using Iter = decltype(v.begin());
+ static_assert(std::is_same_v<Iter::value_type, int>);
+ }
+
+ {
+ //
diff erence_type of multiple views should be the common type
+ std::ranges::concat_view v{DiffTypeRange<std::intptr_t>{}, DiffTypeRange<std::ptr
diff _t>{}};
+ using Iter = decltype(v.begin());
+ static_assert(std::is_same_v<Iter::
diff erence_type,
+ std::common_type_t< std::ranges::range_
diff erence_t<DiffTypeRange<std::intptr_t>>,
+ std::ranges::range_
diff erence_t<DiffTypeRange<std::ptr
diff _t>>>>);
+ }
+
+ {
+ // value_type of multiple views should be the common type
+ std::ranges::concat_view v{DiffTypeRange<std::intptr_t>{}, DiffTypeRange<std::ptr
diff _t>{}};
+ using Iter = decltype(v.begin());
+ static_assert(std::is_same_v<Iter::value_type,
+ std::common_type_t< std::ranges::range_value_t<DiffTypeRange<std::intptr_t>>,
+ std::ranges::range_value_t<DiffTypeRange<std::ptr
diff _t>>>>);
+ }
+
+ const std::array foos{Foo{}};
+ {
+ // value_type of user-defined type
+ std::ranges::concat_view v{foos};
+ using Iter = decltype(v.begin());
+ static_assert(std::is_same_v<Iter::value_type, Foo>);
+ }
+
+ {
+ // const-iterator
diff erent from iterator
+ std::ranges::concat_view v{ConstVeryDifferentRange{}};
+ using Iter = decltype(v.begin());
+ using ConstIter = decltype(std::as_const(v).begin());
+
+ static_assert(std::is_same_v<Iter::iterator_concept, std::random_access_iterator_tag>);
+ static_assert(std::is_same_v<Iter::iterator_category, std::random_access_iterator_tag>);
+ static_assert(std::is_same_v<Iter::
diff erence_type, std::ptr
diff _t>);
+ static_assert(std::is_same_v<Iter::value_type, int>);
+
+ static_assert(std::is_same_v<ConstIter::iterator_concept, std::forward_iterator_tag>);
+ static_assert(std::is_same_v<ConstIter::iterator_category, std::forward_iterator_tag>);
+ static_assert(std::is_same_v<ConstIter::
diff erence_type, std::ptr
diff _t>);
+ static_assert(std::is_same_v<ConstIter::value_type, double>);
+ }
+
+ {
+ // If is_reference_v<concat-reference-t<maybe-const<Const, Views>...>> is false,
+ // then iterator_category denotes input_iterator_tag.
+ auto v1 = std::views::iota(0, 3);
+ auto v2 = std::views::iota(4, 6);
+ auto cat = std::views::concat(v1, v2);
+
+ using Iter = decltype(cat.begin());
+ using ConstIter = decltype(std::as_const(cat).begin());
+
+ static_assert(std::is_same_v<typename Iter::iterator_category, std::input_iterator_tag>);
+ static_assert(std::is_same_v<typename ConstIter::iterator_category, std::input_iterator_tag>);
+ }
+
+ {
+ // iterator category test
+ {
+ // all random-access => iterator_category is random_access_iterator_tag
+ std::span<int> s{buffer}; // span<int> (RA, common)
+ auto sr = std::ranges::subrange{buffer, buffer + 4}; // subrange<pointer,pointer> (RA, common)
+ std::array<int, 2> arr{9, 10}; // array (RA, common)
+ std::ranges::concat_view v(s, sr, std::views::all(arr)); //
diff erent types, all RA + non-last common
+
+ using Iter = decltype(v.begin());
+ using CIter = decltype(std::as_const(v).begin());
+ static_assert(std::is_same_v<typename Iter::iterator_category, std::random_access_iterator_tag>);
+ static_assert(std::is_same_v<typename CIter::iterator_category, std::random_access_iterator_tag>);
+ }
+
+ // random-access + bidirectional => iterator_category is bidirectional_iterator_tag
+ {
+ std::span<int> s{buffer}; // RA
+ std::list<int> lst{1, 2, 3}; // bidirectional
+ std::ranges::concat_view v(s, lst);
+
+ using Iter = decltype(v.begin());
+ using CIter = decltype(std::as_const(v).begin());
+ static_assert(std::is_same_v<typename Iter::iterator_category, std::bidirectional_iterator_tag>);
+ static_assert(std::is_same_v<typename CIter::iterator_category, std::bidirectional_iterator_tag>);
+ }
+
+ // random-access + forward => iterator_category is forward_iterator_tag
+ {
+ std::span<int> s{buffer}; // RA
+ std::forward_list<int> fl{1, 2, 3}; // forward
+ std::ranges::concat_view v(s, fl);
+
+ using Iter = decltype(v.begin());
+ using CIter = decltype(std::as_const(v).begin());
+ static_assert(std::is_same_v<typename Iter::iterator_category, std::forward_iterator_tag>);
+ static_assert(std::is_same_v<typename CIter::iterator_category, std::forward_iterator_tag>);
+ }
+
+ // RA + forward + RA => iterator_category is forward_iterator_tag
+ {
+ std::span<int> s1{buffer}; // RA
+ std::forward_list<int> fl{1, 2}; // forward
+ std::array<int, 1> tail{42}; // RA
+ std::ranges::concat_view v(s1, fl, tail);
+
+ using Iter = decltype(v.begin());
+ using CIter = decltype(std::as_const(v).begin());
+ static_assert(std::is_same_v<typename Iter::iterator_category, std::forward_iterator_tag>);
+ static_assert(std::is_same_v<typename CIter::iterator_category, std::forward_iterator_tag>);
+ }
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.concat/iterator/subscript.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.concat/iterator/subscript.pass.cpp
new file mode 100644
index 0000000000000..a509555711402
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.concat/iterator/subscript.pass.cpp
@@ -0,0 +1,82 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// constexpr auto operator[](
diff erence_type n) const requires
+// all_random_access<Const, Views...>
+
+#include <ranges>
+#include <cassert>
+
+#include "../../range_adaptor_types.h"
+
+constexpr bool test() {
+ int buffer1[8] = {1, 2, 3, 4, 5, 6, 7, 8};
+ int buffer2[8] = {1, 2, 3, 4, 5, 6, 7, 8};
+
+ {
+ // random_access_range and common range
+ std::ranges::concat_view v(SimpleCommonRandomAccessSized{buffer1});
+ auto it = v.begin();
+ assert(it[0] == *it);
+ assert(it[2] == *(it + 2));
+ assert(it[4] == *(it + 4));
+
+ static_assert(std::is_same_v<decltype(it[2]), const int&>);
+ }
+
+ {
+ // random_access_range and common range, with last view is not a common range
+ std::ranges::concat_view v(SimpleCommonRandomAccessSized{buffer1}, SimpleNonCommonRandomAccessSized{buffer2});
+ auto it = v.begin();
+ assert(it[0] == *it);
+ assert(it[2] == *(it + 2));
+ assert(it[4] == *(it + 4));
+
+ static_assert(std::is_same_v<decltype(it[2]), const int&>);
+ }
+
+ {
+ // random_access_range and non common range
+ std::ranges::concat_view v(SimpleNonCommonRandomAccessSized{buffer1}, NonSimpleCommonRandomAccessSized{buffer2});
+ auto it = v.begin();
+ const auto canSubscript = [](auto&& jt) { return requires { jt[0]; }; };
+ static_assert(!canSubscript(it));
+
+ static_assert(std::is_same_v<decltype(*it), const int&>);
+ }
+
+ {
+ // contiguous_range
+ std::ranges::concat_view v(ContiguousCommonView{buffer1}, ContiguousCommonView{buffer2});
+ auto it = v.begin();
+ assert(it[0] == *it);
+ assert(it[2] == *(it + 2));
+ assert(it[4] == *(it + 4));
+
+ static_assert(std::is_same_v<decltype(it[2]), int&>);
+ }
+
+ {
+ // non random_access_range
+ std::ranges::concat_view v(BidiCommonView{buffer1});
+ auto iter = v.begin();
+ const auto canSubscript = [](auto&& it) { return requires { it[0]; }; };
+ static_assert(!canSubscript(iter));
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.concat/size.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.concat/size.pass.cpp
new file mode 100644
index 0000000000000..5502c30cef4e4
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.concat/size.pass.cpp
@@ -0,0 +1,139 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// constexpr auto size()
+// requires(sized_range<_Views> && ...)
+
+// constexpr auto size() const
+// requires(sized_range<const _Views> && ...)
+
+#include <cassert>
+#include <ranges>
+
+#include "test_iterators.h"
+#include "types.h"
+#include "../range_adaptor_types.h"
+
+int buffer[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
+struct View : std::ranges::view_base {
+ std::size_t size_ = 0;
+ constexpr View(std::size_t s) : size_(s) {}
+ constexpr auto begin() const { return buffer; }
+ constexpr auto end() const { return buffer + size_; }
+};
+
+struct SizedNonConst : std::ranges::view_base {
+ using iterator = forward_iterator<int*>;
+ std::size_t size_ = 0;
+ constexpr SizedNonConst(std::size_t s) : size_(s) {}
+ constexpr auto begin() const { return iterator{buffer}; }
+ constexpr auto end() const { return iterator{buffer + size_}; }
+ constexpr std::size_t size() { return size_; }
+};
+
+struct StrangeSizeView : std::ranges::view_base {
+ constexpr auto begin() const { return buffer; }
+ constexpr auto end() const { return buffer + 8; }
+
+ constexpr auto size() { return 5; }
+ constexpr auto size() const { return 6; }
+};
+
+struct NoSizeView : std::ranges::view_base {
+ constexpr auto begin() const { return buffer; }
+ constexpr auto end() const { return buffer + 8; }
+};
+
+struct IntSizeView : std::ranges::view_base {
+ using iterator = forward_iterator<int*>;
+
+ constexpr IntSizeView() {}
+ constexpr auto begin() const { return iterator{buffer}; }
+ constexpr auto end() const { return iterator{buffer + 9}; }
+ constexpr int size() const { return 9; }
+};
+
+struct UnsignedSizeView : std::ranges::view_base {
+ using iterator = forward_iterator<int*>;
+
+ constexpr UnsignedSizeView() {}
+ constexpr auto begin() const { return iterator{buffer}; }
+ constexpr auto end() const { return iterator{buffer + 9}; }
+ constexpr unsigned int size() const { return 9; }
+};
+
+constexpr bool test() {
+ {
+ // single range
+ std::ranges::concat_view v(View(8));
+ assert(v.size() == 8);
+ assert(std::as_const(v).size() == 8);
+ }
+
+ {
+ // multiple ranges same type
+ std::ranges::concat_view v(View(2), View(3));
+ assert(v.size() == 5);
+ assert(std::as_const(v).size() == 5);
+ }
+
+ {
+ // multiple ranges
diff erent types
+ std::ranges::concat_view v(std::views::iota(0, 5), View(3));
+ assert(v.size() == 8);
+ assert(std::as_const(v).size() == 8);
+ }
+
+ {
+ // const-view non-sized range
+ std::ranges::concat_view v(SizedNonConst(2), View(3));
+ assert(v.size() == 5);
+ static_assert(std::ranges::sized_range<decltype(v)>);
+ static_assert(!std::ranges::sized_range<decltype(std::as_const(v))>);
+ }
+
+ {
+ // const/non-const has
diff erent sizes
+ std::ranges::concat_view v(StrangeSizeView{});
+ assert(v.size() == 5);
+ assert(std::as_const(v).size() == 6);
+ }
+
+ {
+ // underlying range not sized
+ std::ranges::concat_view v(InputCommonView{buffer});
+ static_assert(!std::ranges::sized_range<decltype(v)>);
+ static_assert(!std::ranges::sized_range<decltype(std::as_const(v))>);
+ }
+
+ {
+ //two ranges with
diff erent size type
+ std::ranges::concat_view v(UnsignedSizeView{}, IntSizeView{});
+ assert(v.size() == 18);
+ // common type between size_t and int should be size_t
+ ASSERT_SAME_TYPE(decltype(v.size()), unsigned int);
+ }
+
+ {
+ // three ranges with
diff erent size type
+ std::ranges::concat_view v(UnsignedSizeView{}, IntSizeView{}, StrangeSizeView{});
+ assert(v.size() == 23);
+ // common type between size_t and int should be size_t
+ ASSERT_SAME_TYPE(decltype(v.size()), unsigned int);
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.concat/types.h b/libcxx/test/std/ranges/range.adaptors/range.concat/types.h
new file mode 100644
index 0000000000000..d055f8cfbd8dc
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.concat/types.h
@@ -0,0 +1,72 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 TEST_STD_RANGES_RANGE_ADAPTORS_RANGE_CONCAT_TYPES_H
+#define TEST_STD_RANGES_RANGE_ADAPTORS_RANGE_CONCAT_TYPES_H
+
+#include <ranges>
+#include <utility>
+#include "test_iterators.h"
+
+inline int buff[3] = {1, 2, 3};
+
+template <class Iter, class Sent>
+struct minimal_view : std::ranges::view_base {
+ constexpr explicit minimal_view(Iter it, Sent sent) : it_(base(std::move(it))), sent_(base(std::move(sent))) {}
+
+ minimal_view(minimal_view&&) = default;
+ minimal_view& operator=(minimal_view&&) = default;
+
+ constexpr Iter begin() const { return Iter(it_); }
+ constexpr Sent end() const { return Sent(sent_); }
+
+private:
+ decltype(base(std::declval<Iter>())) it_;
+ decltype(base(std::declval<Sent>())) sent_;
+};
+
+struct ViewWithNoConstBegin : std::ranges::view_base {
+ int* begin_;
+ int* end_;
+
+ ViewWithNoConstBegin(int* begin, int* end) : begin_(begin), end_(end) {}
+
+ constexpr int* begin() { return begin_; }
+ constexpr int* end() { return end_; }
+};
+
+struct ViewWithConstBegin : std::ranges::view_base {
+ int* begin_;
+ int* end_;
+
+ ViewWithConstBegin(int* begin, int* end) : begin_(begin), end_(end) {}
+
+ constexpr int* begin() { return begin_; }
+ constexpr int* end() { return end_; }
+
+ constexpr int* begin() const { return begin_; }
+ constexpr int* end() const { return end_; }
+};
+
+struct SizedViewWithConstBegin : std::ranges::view_base {
+ int* begin_;
+ int* end_;
+
+ SizedViewWithConstBegin() : begin_(buff), end_(buff + 3) {}
+
+ constexpr int* begin() { return begin_; }
+ constexpr int* end() { return end_; }
+
+ constexpr int* begin() const { return begin_; }
+ constexpr int* end() const { return end_; }
+};
+
+template <class... Views>
+concept ConcatableConstViews = requires(const std::ranges::concat_view<Views...>& cv) { cv.begin(); };
+
+#endif // TEST_STD_RANGES_RANGE_ADAPTORS_CONCAT_FILTER_TYPES_H
diff --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py
index f81eb0e0e8060..050dab93bea29 100644
--- a/libcxx/utils/generate_feature_test_macro_components.py
+++ b/libcxx/utils/generate_feature_test_macro_components.py
@@ -1125,7 +1125,6 @@ def add_version_header(tc):
"name": "__cpp_lib_ranges_concat",
"values": {"c++26": 202403}, # P2542R8: views::concat
"headers": ["ranges"],
- "unimplemented": True,
},
{
"name": "__cpp_lib_ranges_contains",
More information about the libcxx-commits
mailing list