[libcxx-commits] [libcxx] df324bb - [libcxx][ranges] Add `ranges::join_view`.
via libcxx-commits
libcxx-commits at lists.llvm.org
Fri Aug 13 11:31:20 PDT 2021
Author: zoecarver
Date: 2021-08-13T11:31:08-07:00
New Revision: df324bba5c4cc0309ef4bc756fab4ebb6d67dfba
URL: https://github.com/llvm/llvm-project/commit/df324bba5c4cc0309ef4bc756fab4ebb6d67dfba
DIFF: https://github.com/llvm/llvm-project/commit/df324bba5c4cc0309ef4bc756fab4ebb6d67dfba.diff
LOG: [libcxx][ranges] Add `ranges::join_view`.
Differential Revision: https://reviews.llvm.org/D107671
Added:
libcxx/include/__ranges/join_view.h
libcxx/test/libcxx/diagnostics/detail.headers/ranges/join_view.module.verify.cpp
libcxx/test/std/ranges/range.adaptors/range.join.view/base.pass.cpp
libcxx/test/std/ranges/range.adaptors/range.join.view/begin.pass.cpp
libcxx/test/std/ranges/range.adaptors/range.join.view/ctad.compile.pass.cpp
libcxx/test/std/ranges/range.adaptors/range.join.view/ctad.verify.cpp
libcxx/test/std/ranges/range.adaptors/range.join.view/ctor.base.pass.cpp
libcxx/test/std/ranges/range.adaptors/range.join.view/ctor.default.pass.cpp
libcxx/test/std/ranges/range.adaptors/range.join.view/end.pass.cpp
libcxx/test/std/ranges/range.adaptors/range.join.view/general.pass.cpp
libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/arrow.pass.cpp
libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/ctor.default.pass.cpp
libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/ctor.other.pass.cpp
libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/ctor.parent.outer.pass.cpp
libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/decrement.pass.cpp
libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/eq.pass.cpp
libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/increment.pass.cpp
libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/iter.move.pass.cpp
libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/iter.swap.pass.cpp
libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/member_types.compile.pass.cpp
libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/star.pass.cpp
libcxx/test/std/ranges/range.adaptors/range.join.view/sentinel/ctor.default.pass.cpp
libcxx/test/std/ranges/range.adaptors/range.join.view/sentinel/ctor.other.pass.cpp
libcxx/test/std/ranges/range.adaptors/range.join.view/sentinel/ctor.parent.pass.cpp
libcxx/test/std/ranges/range.adaptors/range.join.view/sentinel/eq.pass.cpp
libcxx/test/std/ranges/range.adaptors/range.join.view/types.h
Modified:
libcxx/docs/Status/RangesPaper.csv
libcxx/include/CMakeLists.txt
libcxx/include/__ranges/non_propagating_cache.h
libcxx/include/module.modulemap
libcxx/include/ranges
libcxx/test/support/test_iterators.h
libcxx/test/support/test_range.h
Removed:
################################################################################
diff --git a/libcxx/docs/Status/RangesPaper.csv b/libcxx/docs/Status/RangesPaper.csv
index 77239c314f787..fcdbe8fb61dd1 100644
--- a/libcxx/docs/Status/RangesPaper.csv
+++ b/libcxx/docs/Status/RangesPaper.csv
@@ -140,7 +140,7 @@ Section,Description,Dependencies,Assignee,Complete
`[range.transform] <http://wg21.link/range.transform>`_,`transform_view <https://llvm.org/D103056>`_,[range.all],Zoe Carver,✅
`[range.iota] <http://wg21.link/range.iota>`_,iota_view,[range.all],Zoe Carver,✅
`[range.take] <http://wg21.link/range.take>`_,take_view,[range.all],Zoe Carver,✅
-`[range.join] <http://wg21.link/range.join>`_,join_view,[range.all],Zoe Carver,In Progress
+`[range.join] <http://wg21.link/range.join>`_,join_view,[range.all],Zoe Carver,✅
`[range.empty] <http://wg21.link/range.empty>`_,`empty_view <https://llvm.org/D103208>`_,[view.interface],Zoe Carver,✅
`[range.single] <http://wg21.link/range.single>`_,single_view,[view.interface],Zoe Carver,✅
`[range.split] <http://wg21.link/range.split>`_,split_view,[range.all],Zoe Carver,In Progress
diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 914c74c7ceb60..f30580e5e8aa5 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -224,6 +224,7 @@ set(files
__ranges/enable_borrowed_range.h
__ranges/enable_view.h
__ranges/iota_view.h
+ __ranges/join_view.h
__ranges/non_propagating_cache.h
__ranges/ref_view.h
__ranges/reverse_view.h
diff --git a/libcxx/include/__ranges/join_view.h b/libcxx/include/__ranges/join_view.h
new file mode 100644
index 0000000000000..44aa1d0264e6d
--- /dev/null
+++ b/libcxx/include/__ranges/join_view.h
@@ -0,0 +1,350 @@
+// -*- 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_JOIN_VIEW_H
+#define _LIBCPP___RANGES_JOIN_VIEW_H
+
+#include <__config>
+#include <__iterator/concepts.h>
+#include <__iterator/iterator_traits.h>
+#include <__ranges/access.h>
+#include <__ranges/all.h>
+#include <__ranges/concepts.h>
+#include <__ranges/non_propagating_cache.h>
+#include <__ranges/ref_view.h>
+#include <__ranges/subrange.h>
+#include <__ranges/view_interface.h>
+#include <__utility/declval.h>
+#include <__utility/forward.h>
+#include <optional>
+#include <type_traits>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#pragma GCC system_header
+#endif
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+#if !defined(_LIBCPP_HAS_NO_RANGES)
+
+namespace ranges {
+ template<class>
+ struct __join_view_iterator_category {};
+
+ template<class _View>
+ requires is_reference_v<range_reference_t<_View>> &&
+ forward_range<_View> &&
+ forward_range<range_reference_t<_View>>
+ struct __join_view_iterator_category<_View> {
+ using _OuterC = typename iterator_traits<iterator_t<_View>>::iterator_category;
+ using _InnerC = typename iterator_traits<iterator_t<range_reference_t<_View>>>::iterator_category;
+
+ using iterator_category = _If<
+ derived_from<_OuterC, bidirectional_iterator_tag> && derived_from<_InnerC, bidirectional_iterator_tag>,
+ bidirectional_iterator_tag,
+ _If<
+ derived_from<_OuterC, forward_iterator_tag> && derived_from<_InnerC, forward_iterator_tag>,
+ forward_iterator_tag,
+ input_iterator_tag
+ >
+ >;
+ };
+
+ template<input_range _View>
+ requires view<_View> && input_range<range_reference_t<_View>>
+ class join_view
+ : public view_interface<join_view<_View>> {
+ private:
+ using _InnerRange = range_reference_t<_View>;
+
+ template<bool> struct __iterator;
+ template<bool> struct __sentinel;
+
+ static constexpr bool _UseCache = !is_reference_v<_InnerRange>;
+ using _Cache = _If<_UseCache, __non_propagating_cache<remove_cvref_t<_InnerRange>>, __empty_cache>;
+ [[no_unique_address]] _Cache __cache_;
+ _View __base_ = _View(); // TODO: [[no_unique_address]] makes clang crash! File a bug :)
+
+ public:
+ _LIBCPP_HIDE_FROM_ABI
+ join_view() requires default_initializable<_View> = default;
+
+ _LIBCPP_HIDE_FROM_ABI
+ constexpr explicit join_view(_View __base)
+ : __base_(_VSTD::move(__base)) {}
+
+ _LIBCPP_HIDE_FROM_ABI
+ constexpr _View base() const& requires copy_constructible<_View> { return __base_; }
+
+ _LIBCPP_HIDE_FROM_ABI
+ constexpr _View base() && { return _VSTD::move(__base_); }
+
+ _LIBCPP_HIDE_FROM_ABI
+ constexpr auto begin() {
+ constexpr bool __use_const = __simple_view<_View> &&
+ is_reference_v<range_reference_t<_View>>;
+ return __iterator<__use_const>{*this, ranges::begin(__base_)};
+ }
+
+ template<class _V2 = _View>
+ _LIBCPP_HIDE_FROM_ABI
+ constexpr auto begin() const
+ requires input_range<const _V2> &&
+ is_reference_v<range_reference_t<const _V2>>
+ {
+ return __iterator<true>{*this, ranges::begin(__base_)};
+ }
+
+ _LIBCPP_HIDE_FROM_ABI
+ constexpr auto end() {
+ if constexpr (forward_range<_View> &&
+ is_reference_v<_InnerRange> &&
+ forward_range<_InnerRange> &&
+ common_range<_View> &&
+ common_range<_InnerRange>)
+ return __iterator<__simple_view<_View>>{*this, ranges::end(__base_)};
+ else
+ return __sentinel<__simple_view<_View>>{*this};
+ }
+
+ template<class _V2 = _View>
+ _LIBCPP_HIDE_FROM_ABI
+ constexpr auto end() const
+ requires input_range<const _V2> &&
+ is_reference_v<range_reference_t<const _V2>>
+ {
+ using _ConstInnerRange = range_reference_t<const _View>;
+ if constexpr (forward_range<const _View> &&
+ is_reference_v<_ConstInnerRange> &&
+ forward_range<_ConstInnerRange> &&
+ common_range<const _View> &&
+ common_range<_ConstInnerRange>) {
+ return __iterator<true>{*this, ranges::end(__base_)};
+ } else {
+ return __sentinel<true>{*this};
+ }
+ }
+ };
+
+ template<input_range _View>
+ requires view<_View> && input_range<range_reference_t<_View>>
+ template<bool _Const> struct join_view<_View>::__sentinel {
+ template<bool> friend struct __sentinel;
+
+ private:
+ using _Parent = __maybe_const<_Const, join_view>;
+ using _Base = __maybe_const<_Const, _View>;
+ sentinel_t<_Base> __end_ = sentinel_t<_Base>();
+
+ public:
+ _LIBCPP_HIDE_FROM_ABI
+ __sentinel() = default;
+
+ _LIBCPP_HIDE_FROM_ABI
+ constexpr explicit __sentinel(_Parent& __parent)
+ : __end_(ranges::end(__parent.__base_)) {}
+
+ _LIBCPP_HIDE_FROM_ABI
+ constexpr __sentinel(__sentinel<!_Const> __s)
+ requires _Const && convertible_to<sentinel_t<_View>, sentinel_t<_Base>>
+ : __end_(_VSTD::move(__s.__end_)) {}
+
+ template<bool _OtherConst>
+ requires sentinel_for<sentinel_t<_Base>, iterator_t<__maybe_const<_OtherConst, _View>>>
+ _LIBCPP_HIDE_FROM_ABI
+ friend constexpr bool operator==(const __iterator<_OtherConst>& __x, const __sentinel& __y) {
+ return __x.__outer_ == __y.__end_;
+ }
+ };
+
+ template<input_range _View>
+ requires view<_View> && input_range<range_reference_t<_View>>
+ template<bool _Const> struct join_view<_View>::__iterator
+ : public __join_view_iterator_category<__maybe_const<_Const, _View>> {
+
+ template<bool> friend struct __iterator;
+
+ private:
+ using _Parent = __maybe_const<_Const, join_view>;
+ using _Base = __maybe_const<_Const, _View>;
+ using _Outer = iterator_t<_Base>;
+ using _Inner = iterator_t<range_reference_t<_Base>>;
+
+ static constexpr bool __ref_is_glvalue = is_reference_v<range_reference_t<_Base>>;
+
+ public:
+ _Outer __outer_ = _Outer();
+
+ private:
+ optional<_Inner> __inner_;
+ _Parent *__parent_ = nullptr;
+
+ _LIBCPP_HIDE_FROM_ABI
+ constexpr void __satisfy() {
+ for (; __outer_ != ranges::end(__parent_->__base_); ++__outer_) {
+ auto&& __inner = [&]() -> auto&& {
+ if constexpr (__ref_is_glvalue)
+ return *__outer_;
+ else
+ return __parent_->__cache_.__emplace_deref(__outer_);
+ }();
+ __inner_ = ranges::begin(__inner);
+ if (*__inner_ != ranges::end(__inner))
+ return;
+ }
+
+ if constexpr (__ref_is_glvalue)
+ __inner_.reset();
+ }
+
+ public:
+ using iterator_concept = _If<
+ __ref_is_glvalue && bidirectional_range<_Base> && bidirectional_range<range_reference_t<_Base>>,
+ bidirectional_iterator_tag,
+ _If<
+ __ref_is_glvalue && forward_range<_Base> && forward_range<range_reference_t<_Base>>,
+ forward_iterator_tag,
+ input_iterator_tag
+ >
+ >;
+
+ using value_type = range_value_t<range_reference_t<_Base>>;
+
+ using
diff erence_type = common_type_t<
+ range_
diff erence_t<_Base>, range_
diff erence_t<range_reference_t<_Base>>>;
+
+ _LIBCPP_HIDE_FROM_ABI
+ __iterator() requires default_initializable<_Outer> = default;
+
+ _LIBCPP_HIDE_FROM_ABI
+ constexpr __iterator(_Parent& __parent, _Outer __outer)
+ : __outer_(_VSTD::move(__outer))
+ , __parent_(_VSTD::addressof(__parent)) {
+ __satisfy();
+ }
+
+ _LIBCPP_HIDE_FROM_ABI
+ constexpr __iterator(__iterator<!_Const> __i)
+ requires _Const &&
+ convertible_to<iterator_t<_View>, _Outer> &&
+ convertible_to<iterator_t<_InnerRange>, _Inner>
+ : __outer_(_VSTD::move(__i.__outer_))
+ , __inner_(_VSTD::move(__i.__inner_))
+ , __parent_(__i.__parent_) {}
+
+ _LIBCPP_HIDE_FROM_ABI
+ constexpr decltype(auto) operator*() const {
+ return **__inner_;
+ }
+
+ _LIBCPP_HIDE_FROM_ABI
+ constexpr _Inner operator->() const
+ requires __has_arrow<_Inner> && copyable<_Inner>
+ {
+ return *__inner_;
+ }
+
+ _LIBCPP_HIDE_FROM_ABI
+ constexpr __iterator& operator++() {
+ auto&& __inner = [&]() -> auto&& {
+ if constexpr (__ref_is_glvalue)
+ return *__outer_;
+ else
+ return *__parent_->__cache_;
+ }();
+ if (++*__inner_ == ranges::end(__inner)) {
+ ++__outer_;
+ __satisfy();
+ }
+ return *this;
+ }
+
+ _LIBCPP_HIDE_FROM_ABI
+ constexpr void operator++(int) {
+ ++*this;
+ }
+
+ _LIBCPP_HIDE_FROM_ABI
+ constexpr __iterator operator++(int)
+ requires __ref_is_glvalue &&
+ forward_range<_Base> &&
+ forward_range<range_reference_t<_Base>>
+ {
+ auto __tmp = *this;
+ ++*this;
+ return __tmp;
+ }
+
+ _LIBCPP_HIDE_FROM_ABI
+ constexpr __iterator& operator--()
+ requires __ref_is_glvalue &&
+ bidirectional_range<_Base> &&
+ bidirectional_range<range_reference_t<_Base>> &&
+ common_range<range_reference_t<_Base>>
+ {
+ if (__outer_ == ranges::end(__parent_->__base_))
+ __inner_ = ranges::end(*--__outer_);
+
+ // Skip empty inner ranges when going backwards.
+ while (*__inner_ == ranges::begin(*__outer_)) {
+ __inner_ = ranges::end(*--__outer_);
+ }
+
+ --*__inner_;
+ return *this;
+ }
+
+ _LIBCPP_HIDE_FROM_ABI
+ constexpr __iterator operator--(int)
+ requires __ref_is_glvalue &&
+ bidirectional_range<_Base> &&
+ bidirectional_range<range_reference_t<_Base>> &&
+ common_range<range_reference_t<_Base>>
+ {
+ auto __tmp = *this;
+ --*this;
+ return __tmp;
+ }
+
+ _LIBCPP_HIDE_FROM_ABI
+ friend constexpr bool operator==(const __iterator& __x, const __iterator& __y)
+ requires __ref_is_glvalue &&
+ equality_comparable<iterator_t<_Base>> &&
+ equality_comparable<iterator_t<range_reference_t<_Base>>>
+ {
+ return __x.__outer_ == __y.__outer_ && __x.__inner_ == __y.__inner_;
+ }
+
+ _LIBCPP_HIDE_FROM_ABI
+ friend constexpr decltype(auto) iter_move(const __iterator& __i)
+ noexcept(noexcept(ranges::iter_move(*__i.__inner_)))
+ {
+ return ranges::iter_move(*__i.__inner_);
+ }
+
+ _LIBCPP_HIDE_FROM_ABI
+ friend constexpr void iter_swap(const __iterator& __x, const __iterator& __y)
+ noexcept(noexcept(ranges::iter_swap(*__x.__inner_, *__y.__inner_)))
+ requires indirectly_swappable<_Inner>
+ {
+ return ranges::iter_swap(*__x.__inner_, *__y.__inner_);
+ }
+ };
+
+ template<class _Range>
+ explicit join_view(_Range&&) -> join_view<views::all_t<_Range>>;
+
+} // namespace ranges
+
+#undef _CONSTEXPR_TERNARY
+
+#endif // !defined(_LIBCPP_HAS_NO_RANGES)
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP___RANGES_JOIN_VIEW_H
diff --git a/libcxx/include/__ranges/non_propagating_cache.h b/libcxx/include/__ranges/non_propagating_cache.h
index 878f7070a07f4..76577f47a5ad4 100644
--- a/libcxx/include/__ranges/non_propagating_cache.h
+++ b/libcxx/include/__ranges/non_propagating_cache.h
@@ -85,6 +85,14 @@ namespace ranges {
constexpr void __set(_Tp const& __value) { __value_.emplace(__value); }
_LIBCPP_HIDE_FROM_ABI
constexpr void __set(_Tp&& __value) { __value_.emplace(_VSTD::move(__value)); }
+
+ template<class _Other>
+ _LIBCPP_HIDE_FROM_ABI
+ constexpr _Tp& __emplace_deref(const _Other& __value) {
+ __value_.reset();
+ __value_.emplace(*__value);
+ return *__value_;
+ }
};
struct __empty_cache { };
diff --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap
index 079dff201804b..f9955a3cd0c3b 100644
--- a/libcxx/include/module.modulemap
+++ b/libcxx/include/module.modulemap
@@ -659,6 +659,7 @@ module std [system] {
module enable_borrowed_range { private header "__ranges/enable_borrowed_range.h" }
module enable_view { private header "__ranges/enable_view.h" }
module iota_view { private header "__ranges/iota_view.h" }
+ module join_view { private header "__ranges/join_view.h" }
module non_propagating_cache { private header "__ranges/non_propagating_cache.h" }
module ref_view { private header "__ranges/ref_view.h" }
module reverse_view { private header "__ranges/reverse_view.h" }
diff --git a/libcxx/include/ranges b/libcxx/include/ranges
index df8d4194ffa14..014260aaee15b 100644
--- a/libcxx/include/ranges
+++ b/libcxx/include/ranges
@@ -184,6 +184,11 @@ namespace std::ranges {
template<class W, class Bound>
inline constexpr bool enable_borrowed_range<iota_view<W, Bound>> = true;
+
+ // [range.join], join view
+ template<input_range V>
+ requires view<V> && input_range<range_reference_t<V>>
+ class join_view;
}
*/
@@ -207,6 +212,7 @@ namespace std::ranges {
#include <__ranges/enable_borrowed_range.h>
#include <__ranges/enable_view.h>
#include <__ranges/iota_view.h>
+#include <__ranges/join_view.h>
#include <__ranges/ref_view.h>
#include <__ranges/reverse_view.h>
#include <__ranges/take_view.h>
diff --git a/libcxx/test/libcxx/diagnostics/detail.headers/ranges/join_view.module.verify.cpp b/libcxx/test/libcxx/diagnostics/detail.headers/ranges/join_view.module.verify.cpp
new file mode 100644
index 0000000000000..ff48b744d5d93
--- /dev/null
+++ b/libcxx/test/libcxx/diagnostics/detail.headers/ranges/join_view.module.verify.cpp
@@ -0,0 +1,16 @@
+// -*- 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: modules-build
+
+// WARNING: This test was generated by 'generate_private_header_tests.py'
+// and should not be edited manually.
+
+// expected-error@*:* {{use of private header from outside its module: '__ranges/join_view.h'}}
+#include <__ranges/join_view.h>
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/base.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/base.pass.cpp
new file mode 100644
index 0000000000000..60cbb26f2502d
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/base.pass.cpp
@@ -0,0 +1,62 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// constexpr V base() const& requires copy_constructible<V>;
+// constexpr V base() &&;
+
+#include <cassert>
+#include <ranges>
+
+#include "test_macros.h"
+#include "types.h"
+
+constexpr bool hasLValueQualifiedBase(auto&& view) {
+ return requires { view.base(); };
+}
+
+constexpr bool test() {
+ int buffer[4][4] = {{1111, 2222, 3333, 4444}, {555, 666, 777, 888}, {99, 1010, 1111, 1212}, {13, 14, 15, 16}};
+
+ {
+ ChildView children[4] = {ChildView(buffer[0]), ChildView(buffer[1]), ChildView(buffer[2]), ChildView(buffer[3])};
+ auto jv = std::ranges::join_view(ParentView{children});
+ assert(std::move(jv).base().ptr_ == children);
+
+ static_assert(!hasLValueQualifiedBase(jv));
+ ASSERT_SAME_TYPE(decltype(std::move(jv).base()), ParentView<ChildView>);
+ }
+
+ {
+ std::ranges::join_view jv(buffer);
+ assert(jv.base().base() == buffer + 0);
+
+ static_assert(hasLValueQualifiedBase(jv));
+ ASSERT_SAME_TYPE(decltype(jv.base()), std::ranges::ref_view<int [4][4]>);
+ }
+
+ {
+ const std::ranges::join_view jv(buffer);
+ assert(jv.base().base() == buffer + 0);
+
+ static_assert(hasLValueQualifiedBase(jv));
+ ASSERT_SAME_TYPE(decltype(jv.base()), std::ranges::ref_view<int [4][4]>);
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/begin.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/begin.pass.cpp
new file mode 100644
index 0000000000000..2441f6787f2ba
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/begin.pass.cpp
@@ -0,0 +1,97 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// constexpr auto begin();
+// constexpr auto begin() const;
+
+#include <cassert>
+#include <ranges>
+
+#include "test_macros.h"
+#include "types.h"
+
+constexpr bool test() {
+ int buffer[4][4] = {{1111, 2222, 3333, 4444}, {555, 666, 777, 888}, {99, 1010, 1111, 1212}, {13, 14, 15, 16}};
+
+ {
+ ChildView children[4] = {ChildView(buffer[0]), ChildView(buffer[1]), ChildView(buffer[2]), ChildView(buffer[3])};
+ auto jv = std::ranges::join_view(ParentView{children});
+ assert(*jv.begin() == 1111);
+ }
+
+ {
+ CopyableChild children[4] = {CopyableChild(buffer[0], 4), CopyableChild(buffer[1], 0), CopyableChild(buffer[2], 1), CopyableChild(buffer[3], 0)};
+ auto jv = std::ranges::join_view(ParentView{children});
+ assert(*jv.begin() == 1111);
+ }
+ // Parent is empty.
+ {
+ CopyableChild children[4] = {CopyableChild(buffer[0]), CopyableChild(buffer[1]), CopyableChild(buffer[2]), CopyableChild(buffer[3])};
+ std::ranges::join_view jv(ParentView(children, 0));
+ assert(jv.begin() == jv.end());
+ }
+ // Parent size is one.
+ {
+ CopyableChild children[1] = {CopyableChild(buffer[0])};
+ std::ranges::join_view jv(ParentView(children, 1));
+ assert(*jv.begin() == 1111);
+ }
+ // Parent and child size is one.
+ {
+ CopyableChild children[1] = {CopyableChild(buffer[0], 1)};
+ std::ranges::join_view jv(ParentView(children, 1));
+ assert(*jv.begin() == 1111);
+ }
+ // Parent size is one child is empty
+ {
+ CopyableChild children[1] = {CopyableChild(buffer[0], 0)};
+ std::ranges::join_view jv(ParentView(children, 1));
+ assert(jv.begin() == jv.end());
+ }
+ // Has all empty children.
+ {
+ CopyableChild children[4] = {CopyableChild(buffer[0], 0), CopyableChild(buffer[1], 0), CopyableChild(buffer[2], 0), CopyableChild(buffer[3], 0)};
+ auto jv = std::ranges::join_view(ParentView{children});
+ assert(jv.begin() == jv.end());
+ }
+ // First child is empty, others are not.
+ {
+ CopyableChild children[4] = {CopyableChild(buffer[0], 4), CopyableChild(buffer[1], 0), CopyableChild(buffer[2], 0), CopyableChild(buffer[3], 0)};
+ auto jv = std::ranges::join_view(ParentView{children});
+ assert(*jv.begin() == 1111);
+ }
+ // Last child is empty, others are not.
+ {
+ CopyableChild children[4] = {CopyableChild(buffer[0], 4), CopyableChild(buffer[1], 4), CopyableChild(buffer[2], 4), CopyableChild(buffer[3], 0)};
+ auto jv = std::ranges::join_view(ParentView{children});
+ assert(*jv.begin() == 1111);
+ }
+
+ {
+ std::ranges::join_view jv(buffer);
+ assert(*jv.begin() == 1111);
+ }
+
+ {
+ const std::ranges::join_view jv(buffer);
+ assert(*jv.begin() == 1111);
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/ctad.compile.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/ctad.compile.pass.cpp
new file mode 100644
index 0000000000000..a81fa03e15c21
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/ctad.compile.pass.cpp
@@ -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
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// template<class R>
+// explicit join_view(R&&) -> join_view<views::all_t<R>>;
+
+#include <ranges>
+
+#include "test_iterators.h"
+
+template<class T>
+struct View : std::ranges::view_base {
+ // All friends here are defined to prevent GCC warnings.
+ friend T* begin(View&) { return nullptr; }
+ friend T* begin(View const&) { return nullptr; }
+ friend sentinel_wrapper<T*> end(View&) { return sentinel_wrapper<T*>(nullptr); }
+ friend sentinel_wrapper<T*> end(View const&) { return sentinel_wrapper<T*>(nullptr); }
+};
+
+template<class T>
+struct Range {
+ friend T* begin(Range&) { return nullptr; }
+ friend T* begin(Range const&) { return nullptr; }
+ friend sentinel_wrapper<T*> end(Range&) { return sentinel_wrapper<T*>(nullptr); }
+ friend sentinel_wrapper<T*> end(Range const&) { return sentinel_wrapper<T*>(nullptr); }
+};
+
+template<class T>
+struct BorrowedRange {
+ friend T* begin(BorrowedRange&) { return nullptr; }
+ friend T* begin(BorrowedRange const&) { return nullptr; }
+ friend sentinel_wrapper<T*> end(BorrowedRange&) { return sentinel_wrapper<T*>(nullptr); }
+ friend sentinel_wrapper<T*> end(BorrowedRange const&) { return sentinel_wrapper<T*>(nullptr); }
+};
+
+template<>
+inline constexpr bool std::ranges::enable_borrowed_range<BorrowedRange<BorrowedRange<int>>> = true;
+
+void testCTAD() {
+ View<View<int>> v;
+ Range<Range<int>> r;
+ BorrowedRange<BorrowedRange<int>> br;
+
+ static_assert(std::same_as<
+ decltype(std::ranges::join_view(v)),
+ std::ranges::join_view<View<View<int>>>
+ >);
+ static_assert(std::same_as<
+ decltype(std::ranges::join_view(r)),
+ std::ranges::join_view<std::ranges::ref_view<Range<Range<int>>>>
+ >);
+ // std::ranges::join_view(std::move(r)) invalid. RValue range must be borrowed.
+ static_assert(std::same_as<
+ decltype(std::ranges::join_view(br)),
+ std::ranges::join_view<std::ranges::ref_view<BorrowedRange<BorrowedRange<int>>>>
+ >);
+ static_assert(std::same_as<
+ decltype(std::ranges::join_view(std::move(br))),
+ std::ranges::join_view<std::ranges::subrange<BorrowedRange<int> *,
+ sentinel_wrapper<BorrowedRange<int> *>,
+ std::ranges::subrange_kind::unsized>>
+ >);
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/ctad.verify.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/ctad.verify.cpp
new file mode 100644
index 0000000000000..1bdd1e62eeb64
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/ctad.verify.cpp
@@ -0,0 +1,33 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// template<class R>
+// explicit join_view(R&&) -> join_view<views::all_t<R>>;
+
+// Tests that the deduction guide is explicit.
+
+#include <ranges>
+
+#include "test_iterators.h"
+
+template<class T>
+struct Range {
+ friend T* begin(Range&) { return nullptr; }
+ friend T* begin(Range const&) { return nullptr; }
+ friend sentinel_wrapper<T*> end(Range&) { return sentinel_wrapper<T*>(nullptr); }
+ friend sentinel_wrapper<T*> end(Range const&) { return sentinel_wrapper<T*>(nullptr); }
+};
+
+void testExplicitCTAD() {
+ Range<Range<int>> r;
+ std::ranges::join_view v = r; // expected-error {{no viable constructor or deduction guide for deduction of template arguments of 'join_view'}}
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/ctor.base.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/ctor.base.pass.cpp
new file mode 100644
index 0000000000000..2cdbe3b0268c4
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/ctor.base.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
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// constexpr explicit join_view(V base);
+
+#include <cassert>
+#include <ranges>
+
+#include "test_macros.h"
+#include "types.h"
+
+constexpr bool test() {
+ int buffer[4][4] = {{1111, 2222, 3333, 4444}, {555, 666, 777, 888}, {99, 1010, 1111, 1212}, {13, 14, 15, 16}};
+
+ {
+ ChildView children[4] = {ChildView(buffer[0]), ChildView(buffer[1]), ChildView(buffer[2]), ChildView(buffer[3])};
+ auto jv = std::ranges::join_view(ParentView{children});
+ assert(std::move(jv).base().ptr_ == children);
+ }
+
+ {
+ std::ranges::join_view jv(buffer);
+ assert(jv.base().base() == buffer + 0);
+ }
+
+ {
+ // Test explicitness.
+ static_assert( std::is_constructible_v<std::ranges::join_view<ParentView<ChildView>>, ParentView<ChildView>>);
+ static_assert(!std::is_convertible_v<std::ranges::join_view<ParentView<ChildView>>, ParentView<ChildView>>);
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/ctor.default.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/ctor.default.pass.cpp
new file mode 100644
index 0000000000000..ff93d8aa6fdf8
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/ctor.default.pass.cpp
@@ -0,0 +1,37 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// join_view() requires default_initializable<V> = default;
+
+#include <cassert>
+#include <ranges>
+
+#include "test_macros.h"
+#include "types.h"
+
+
+constexpr bool test() {
+ std::ranges::join_view<ParentView<ChildView>> jv;
+ assert(std::move(jv).base().ptr_ == globalChildren);
+
+ static_assert( std::default_initializable<std::ranges::join_view<ParentView<ChildView>>>);
+ static_assert(!std::default_initializable<std::ranges::join_view<CopyableParent>>);
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/end.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/end.pass.cpp
new file mode 100644
index 0000000000000..33ef7a7374d3e
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/end.pass.cpp
@@ -0,0 +1,120 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// constexpr auto end();
+// constexpr auto end() const;
+
+#include <cassert>
+#include <ranges>
+#include <type_traits>
+
+#include "test_macros.h"
+#include "types.h"
+
+
+constexpr bool test() {
+ int buffer[4][4] = {{1111, 2222, 3333, 4444}, {555, 666, 777, 888}, {99, 1010, 1111, 1212}, {13, 14, 15, 16}};
+
+ // Non const common, forward range.
+ {
+ std::ranges::join_view jv(buffer);
+ assert(jv.end() == std::ranges::next(jv.begin(), 16));
+
+ static_assert(std::same_as<decltype(jv.end()), decltype(jv.begin())>);
+ }
+
+ // Non const not common, input range.
+ {
+ ChildView children[4] = {ChildView(buffer[0]), ChildView(buffer[1]), ChildView(buffer[2]), ChildView(buffer[3])};
+ auto jv = std::ranges::join_view(ParentView(children));
+ assert(jv.end() == std::ranges::next(jv.begin(), 16));
+
+ static_assert(!std::same_as<decltype(jv.end()), decltype(jv.begin())>);
+ }
+
+ // Const common, forward range.
+ {
+ const std::ranges::join_view jv(buffer);
+ assert(jv.end() == std::ranges::next(jv.begin(), 16));
+
+ static_assert(std::same_as<decltype(jv.end()), decltype(jv.begin())>);
+ }
+
+ // Const not common, input range.
+ {
+ static_assert(std::is_reference_v<std::ranges::range_reference_t<const CopyableParent>>);
+
+ CopyableChild children[4] = {CopyableChild(buffer[0]), CopyableChild(buffer[1]), CopyableChild(buffer[2]), CopyableChild(buffer[3])};
+ const auto jv = std::ranges::join_view(ParentView(children));
+ assert(jv.end() == std::ranges::next(jv.begin(), 16));
+
+ static_assert(!std::same_as<decltype(jv.end()), decltype(jv.begin())>);
+ }
+
+ // Has some empty children.
+ {
+ CopyableChild children[4] = {CopyableChild(buffer[0], 4), CopyableChild(buffer[1], 0), CopyableChild(buffer[2], 1), CopyableChild(buffer[3], 0)};
+ auto jv = std::ranges::join_view(ParentView(children));
+ assert(jv.end() == std::ranges::next(jv.begin(), 5));
+ }
+ // Parent is empty.
+ {
+ CopyableChild children[4] = {CopyableChild(buffer[0]), CopyableChild(buffer[1]), CopyableChild(buffer[2]), CopyableChild(buffer[3])};
+ std::ranges::join_view jv(ParentView(children, 0));
+ assert(jv.end() == jv.begin());
+ }
+ // Parent size is one.
+ {
+ CopyableChild children[1] = {CopyableChild(buffer[0])};
+ std::ranges::join_view jv(ParentView(children, 1));
+ assert(jv.end() == std::ranges::next(jv.begin(), 4));
+ }
+ // Parent and child size is one.
+ {
+ CopyableChild children[1] = {CopyableChild(buffer[0], 1)};
+ std::ranges::join_view jv(ParentView(children, 1));
+ assert(jv.end() == std::ranges::next(jv.begin()));
+ }
+ // Parent size is one child is empty
+ {
+ CopyableChild children[1] = {CopyableChild(buffer[0], 0)};
+ std::ranges::join_view jv(ParentView(children, 1));
+ assert(jv.end() == jv.begin());
+ }
+ // Has all empty children.
+ {
+ CopyableChild children[4] = {CopyableChild(buffer[0], 0), CopyableChild(buffer[1], 0), CopyableChild(buffer[2], 0), CopyableChild(buffer[3], 0)};
+ auto jv = std::ranges::join_view(ParentView(children));
+ assert(jv.end() == jv.begin());
+ }
+ // First child is empty, others are not.
+ {
+ CopyableChild children[4] = {CopyableChild(buffer[0], 4), CopyableChild(buffer[1], 0), CopyableChild(buffer[2], 0), CopyableChild(buffer[3], 0)};
+ auto jv = std::ranges::join_view(ParentView(children));
+ assert(jv.end() == std::ranges::next(jv.begin(), 4));
+ }
+ // Last child is empty, others are not.
+ {
+ CopyableChild children[4] = {CopyableChild(buffer[0], 4), CopyableChild(buffer[1], 4), CopyableChild(buffer[2], 4), CopyableChild(buffer[3], 0)};
+ auto jv = std::ranges::join_view(ParentView(children));
+ assert(jv.end() == std::ranges::next(jv.begin(), 12));
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/general.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/general.pass.cpp
new file mode 100644
index 0000000000000..e0fb8a8c6ddc1
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/general.pass.cpp
@@ -0,0 +1,51 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// General tests for join_view. This file does not test anything specifically.
+
+#include <algorithm>
+#include <cassert>
+#include <ranges>
+#include <string>
+#include <vector>
+
+#include "test_macros.h"
+#include "types.h"
+
+
+template<class R, class I>
+bool isEqual(R &r, I i) {
+ for (auto e : r)
+ if (e != *i++)
+ return false;
+ return true;
+}
+
+int main(int, char**) {
+ {
+ int buffer[4][4] = {{1111, 2222, 3333, 4444}, {555, 666, 777, 888}, {99, 1010, 1111, 1212}, {13, 14, 15, 16}};
+ int *flattened = reinterpret_cast<int*>(buffer);
+
+ ChildView children[4] = {ChildView(buffer[0]), ChildView(buffer[1]), ChildView(buffer[2]), ChildView(buffer[3])};
+ auto jv = std::ranges::join_view(ParentView(children));
+ assert(isEqual(jv, flattened));
+ }
+
+ {
+ std::vector<std::string> vec = {"Hello", ",", " ", "World", "!"};
+ std::string check = "Hello, World!";
+ std::ranges::join_view jv(vec);
+ assert(isEqual(jv, check.begin()));
+ }
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/arrow.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/arrow.pass.cpp
new file mode 100644
index 0000000000000..1579f56151645
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/arrow.pass.cpp
@@ -0,0 +1,50 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// constexpr InnerIter operator->() const
+// requires has-arrow<InnerIter> && copyable<InnerIter>;
+
+#include <cassert>
+#include <ranges>
+
+#include "test_macros.h"
+#include "../types.h"
+
+constexpr bool test() {
+ Box buffer[4][4] = {{{1111}, {2222}, {3333}, {4444}}, {{555}, {666}, {777}, {888}}, {{99}, {1010}, {1111}, {1212}}, {{13}, {14}, {15}, {16}}};
+
+ {
+ // Copyable input iterator with arrow.
+ ValueView<Box> children[4] = {ValueView(buffer[0]), ValueView(buffer[1]), ValueView(buffer[2]), ValueView(buffer[3])};
+ std::ranges::join_view jv(ValueView<ValueView<Box>>{children});
+ assert(jv.begin()->x == 1111);
+ }
+
+ {
+ std::ranges::join_view jv(buffer);
+ assert(jv.begin()->x == 1111);
+ }
+
+ {
+ const std::ranges::join_view jv(buffer);
+ assert(jv.begin()->x == 1111);
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/ctor.default.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/ctor.default.pass.cpp
new file mode 100644
index 0000000000000..52bae5bb752fb
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/ctor.default.pass.cpp
@@ -0,0 +1,56 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// iterator() requires default_initializable<OuterIter> = default;
+
+#include <cassert>
+#include <ranges>
+
+#include "test_macros.h"
+#include "../types.h"
+
+template<class T>
+struct DefaultCtorParent : std::ranges::view_base {
+ T *ptr_;
+ constexpr DefaultCtorParent(T *ptr) : ptr_(ptr) {}
+
+ constexpr cpp17_input_iterator<T *> begin() { return cpp17_input_iterator<T *>(ptr_); }
+ constexpr cpp17_input_iterator<const T *> begin() const { return cpp17_input_iterator<const T *>(ptr_); }
+ constexpr T *end() { return ptr_ + 4; }
+ constexpr const T *end() const { return ptr_ + 4; }
+};
+
+template<class T>
+constexpr bool operator==(const cpp17_input_iterator<T*> &lhs, const T *rhs) { return lhs.base() == rhs; }
+template<class T>
+constexpr bool operator==(const T *lhs, const cpp17_input_iterator<T*> &rhs) { return rhs.base() == lhs; }
+
+constexpr bool test() {
+ using Base = DefaultCtorParent<ChildView>;
+ // Note, only the outer iterator is default_initializable:
+ static_assert( std::default_initializable<std::ranges::iterator_t<Base>>);
+ static_assert(!std::default_initializable<std::ranges::iterator_t<std::ranges::range_reference_t<Base>>>);
+
+ std::ranges::iterator_t<std::ranges::join_view<Base>> iter1;
+ (void) iter1;
+
+ static_assert(!std::default_initializable<std::ranges::iterator_t<std::ranges::join_view<ParentView<ChildView>>>>);
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/ctor.other.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/ctor.other.pass.cpp
new file mode 100644
index 0000000000000..87290c4baec86
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/ctor.other.pass.cpp
@@ -0,0 +1,41 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// constexpr iterator(iterator<!Const> i);
+
+#include <cassert>
+#include <ranges>
+
+#include "test_macros.h"
+#include "../types.h"
+
+constexpr bool test() {
+ int buffer[4][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 16}};
+
+ CopyableChild children[4] = {CopyableChild(buffer[0]), CopyableChild(buffer[1]), CopyableChild(buffer[2]), CopyableChild(buffer[3])};
+ std::ranges::join_view jv(CopyableParent{children});
+ auto iter1 = jv.begin();
+ std::ranges::iterator_t<const decltype(jv)> iter2 = iter1;
+ assert(iter1 == iter2);
+
+ // We cannot create a non-const iterator from a const iterator.
+ static_assert(!std::constructible_from<decltype(iter1), decltype(iter2)>);
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/ctor.parent.outer.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/ctor.parent.outer.pass.cpp
new file mode 100644
index 0000000000000..ae6ca21e72a94
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/ctor.parent.outer.pass.cpp
@@ -0,0 +1,38 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// constexpr iterator(Parent& parent, OuterIter outer);
+
+#include <cassert>
+#include <ranges>
+
+#include "test_macros.h"
+#include "../types.h"
+
+constexpr bool test() {
+ int buffer[4][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 16}};
+
+ CopyableChild children[4] = {CopyableChild(buffer[0]), CopyableChild(buffer[1]), CopyableChild(buffer[2]), CopyableChild(buffer[3])};
+ CopyableParent parent{children};
+ std::ranges::join_view jv(parent);
+ std::ranges::iterator_t<decltype(jv)> iter(jv, std::ranges::begin(parent));
+ assert(*iter == 1);
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/decrement.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/decrement.pass.cpp
new file mode 100644
index 0000000000000..66b3749365461
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/decrement.pass.cpp
@@ -0,0 +1,74 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// constexpr iterator& operator--();
+// constexpr iterator operator--(int);
+
+#include <cassert>
+#include <ranges>
+
+#include "test_macros.h"
+#include "../types.h"
+
+constexpr bool test() {
+ int buffer[4][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 16}};
+
+ {
+ // outer == ranges::end
+ std::ranges::join_view jv(buffer);
+ auto iter = std::next(jv.begin(), 16);
+ for (int i = 16; i != 0; --i) {
+ assert(*--iter == i);
+ }
+ }
+ {
+ // outer == ranges::end
+ std::ranges::join_view jv(buffer);
+ auto iter = std::next(jv.begin(), 13);
+ for (int i = 13; i != 0; --i) {
+ assert(*--iter == i);
+ }
+ }
+ {
+ // outer != ranges::end
+ std::ranges::join_view jv(buffer);
+ auto iter = std::next(jv.begin(), 12);
+ for (int i = 12; i != 0; --i) {
+ assert(*--iter == i);
+ }
+ }
+ {
+ // outer != ranges::end
+ std::ranges::join_view jv(buffer);
+ auto iter = std::next(jv.begin());
+ for (int i = 1; i != 0; --i) {
+ assert(*--iter == i);
+ }
+ }
+ {
+ int small[2][1] = {{1}, {2}};
+ std::ranges::join_view jv(small);
+ auto iter = std::next(jv.begin(), 2);
+ for (int i = 2; i != 0; --i) {
+ assert(*--iter == i);
+ }
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/eq.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/eq.pass.cpp
new file mode 100644
index 0000000000000..b76f72a453cc5
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/eq.pass.cpp
@@ -0,0 +1,41 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// friend constexpr bool operator==(const iterator& x, const iterator& y);
+
+#include <cassert>
+#include <ranges>
+
+#include "test_macros.h"
+#include "../types.h"
+
+constexpr bool test() {
+ int buffer[4][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 16}};
+
+ std::ranges::join_view jv(buffer);
+ auto iter1 = jv.begin();
+ auto iter2 = jv.begin();
+ assert(iter1 == iter2);
+ iter1++;
+ assert(iter1 != iter2);
+ iter2++;
+ assert(iter1 == iter2);
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/increment.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/increment.pass.cpp
new file mode 100644
index 0000000000000..853ed1a27f577
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/increment.pass.cpp
@@ -0,0 +1,160 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// constexpr iterator& operator++();
+// constexpr void operator++(int);
+// constexpr iterator operator++(int);
+
+#include <cassert>
+#include <ranges>
+
+#include "test_macros.h"
+#include "../types.h"
+
+constexpr bool test() {
+ // This way if we read past end we'll catch the error.
+ int buffer1[2][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}};
+ int dummy = 42;
+ (void) dummy;
+ int buffer2[2][4] = {{9, 10, 11, 12}, {13, 14, 15, 16}};
+
+ // operator++(int);
+ {
+ std::ranges::join_view jv(buffer1);
+ auto iter = jv.begin();
+ for (int i = 1; i < 9; ++i) {
+ assert(*iter++ == i);
+ }
+ }
+ {
+ ValueView<int> children[4] = {ValueView(buffer1[0]), ValueView(buffer1[1]), ValueView(buffer2[0]), ValueView(buffer2[1])};
+ std::ranges::join_view jv(ValueView<ValueView<int>>{children});
+ auto iter = jv.begin();
+ for (int i = 1; i < 17; ++i) {
+ assert(*iter == i);
+ iter++;
+ }
+
+ ASSERT_SAME_TYPE(decltype(iter++), void);
+ }
+ {
+ std::ranges::join_view jv(buffer1);
+ auto iter = std::next(jv.begin(), 7);
+ assert(*iter++ == 8);
+ assert(iter == jv.end());
+ }
+ {
+ int small[2][1] = {{1}, {2}};
+ std::ranges::join_view jv(small);
+ auto iter = jv.begin();
+ for (int i = 1; i < 3; ++i) {
+ assert(*iter++ == i);
+ }
+ }
+ // Has some empty children.
+ {
+ CopyableChild children[4] = {CopyableChild(buffer1[0], 4), CopyableChild(buffer1[1], 0), CopyableChild(buffer2[0], 1), CopyableChild(buffer2[1], 0)};
+ auto jv = std::ranges::join_view(ParentView(children));
+ auto iter = jv.begin();
+ assert(*iter == 1); iter++;
+ assert(*iter == 2); iter++;
+ assert(*iter == 3); iter++;
+ assert(*iter == 4); iter++;
+ assert(*iter == 9); iter++;
+ assert(iter == jv.end());
+ }
+ // Parent is empty.
+ {
+ CopyableChild children[4] = {CopyableChild(buffer1[0]), CopyableChild(buffer1[1]), CopyableChild(buffer2[0]), CopyableChild(buffer2[1])};
+ std::ranges::join_view jv(ParentView(children, 0));
+ assert(jv.begin() == jv.end());
+ }
+ // Parent size is one.
+ {
+ CopyableChild children[1] = {CopyableChild(buffer1[0])};
+ std::ranges::join_view jv(ParentView(children, 1));
+ auto iter = jv.begin();
+ assert(*iter == 1); iter++;
+ assert(*iter == 2); iter++;
+ assert(*iter == 3); iter++;
+ assert(*iter == 4); iter++;
+ assert(iter == jv.end());
+ }
+ // Parent and child size is one.
+ {
+ CopyableChild children[1] = {CopyableChild(buffer1[0], 1)};
+ std::ranges::join_view jv(ParentView(children, 1));
+ auto iter = jv.begin();
+ assert(*iter == 1); iter++;
+ assert(iter == jv.end());
+ }
+ // Parent size is one child is empty
+ {
+ CopyableChild children[1] = {CopyableChild(buffer1[0], 0)};
+ std::ranges::join_view jv(ParentView(children, 1));
+ assert(jv.begin() == jv.end());
+ }
+ // Has all empty children.
+ {
+ CopyableChild children[4] = {CopyableChild(buffer1[0], 0), CopyableChild(buffer1[1], 0), CopyableChild(buffer2[0], 0), CopyableChild(buffer2[1], 0)};
+ auto jv = std::ranges::join_view(ParentView(children));
+ assert(jv.begin() == jv.end());
+ }
+ // First child is empty, others are not.
+ {
+ CopyableChild children[4] = {CopyableChild(buffer1[0], 4), CopyableChild(buffer1[1], 0), CopyableChild(buffer2[0], 0), CopyableChild(buffer2[1], 0)};
+ auto jv = std::ranges::join_view(ParentView(children));
+ auto iter = jv.begin();
+ assert(*iter == 1); iter++;
+ assert(*iter == 2); iter++;
+ assert(*iter == 3); iter++;
+ assert(*iter == 4); iter++;
+ assert(iter == jv.end());
+ }
+ // Last child is empty, others are not.
+ {
+ CopyableChild children[4] = {CopyableChild(buffer1[0], 4), CopyableChild(buffer1[1], 4), CopyableChild(buffer2[0], 4), CopyableChild(buffer2[1], 0)};
+ auto jv = std::ranges::join_view(ParentView(children));
+ auto iter = jv.begin();
+ for (int i = 1; i < 13; ++i) {
+ assert(*iter == i);
+ iter++;
+ }
+ }
+ // operator++();
+ {
+ std::ranges::join_view jv(buffer1);
+ auto iter = jv.begin();
+ for (int i = 2; i < 9; ++i) {
+ assert(*++iter == i);
+ }
+ }
+ {
+ ValueView<int> children[4] = {ValueView(buffer1[0]), ValueView(buffer1[1]), ValueView(buffer2[0]), ValueView(buffer2[1])};
+ std::ranges::join_view jv(ValueView<ValueView<int>>{children});
+ auto iter = jv.begin();
+ for (int i = 2; i < 17; ++i) {
+ assert(*++iter == i);
+ }
+
+ ASSERT_SAME_TYPE(decltype(++iter), decltype(iter)&);
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/iter.move.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/iter.move.pass.cpp
new file mode 100644
index 0000000000000..b3e3fd2dade71
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/iter.move.pass.cpp
@@ -0,0 +1,38 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// friend constexpr decltype(auto) iter_move(const iterator& i);
+
+#include <cassert>
+#include <ranges>
+
+#include "test_macros.h"
+#include "../types.h"
+
+constexpr bool test() {
+ int buffer[4][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 16}};
+
+ std::ranges::join_view jv(buffer);
+ assert(std::ranges::iter_move(jv.begin()) == 1);
+ ASSERT_SAME_TYPE(decltype(std::ranges::iter_move(jv.begin())), int&&);
+
+ static_assert(noexcept(std::ranges::iter_move(std::declval<decltype(jv.begin())>())));
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/iter.swap.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/iter.swap.pass.cpp
new file mode 100644
index 0000000000000..30d61f516df89
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/iter.swap.pass.cpp
@@ -0,0 +1,43 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// friend constexpr void iter_swap(const iterator& x, const iterator& y);
+
+#include <cassert>
+#include <ranges>
+
+#include "test_macros.h"
+#include "../types.h"
+
+constexpr bool test() {
+ int buffer[4][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 16}};
+
+ std::ranges::join_view jv(buffer);
+ auto iter1 = jv.begin();
+ auto iter2 = std::next(jv.begin());
+ assert(*iter1 == 1);
+ assert(*iter2 == 2);
+ std::ranges::swap(iter1, iter2);
+ assert(*iter1 == 2);
+ assert(*iter2 == 1);
+
+ static_assert(noexcept(std::ranges::iter_swap(iter1, iter2)));
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/member_types.compile.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/member_types.compile.pass.cpp
new file mode 100644
index 0000000000000..acf7ca17cd69b
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/member_types.compile.pass.cpp
@@ -0,0 +1,67 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// Iterator traits and member typedefs in join_view::<iterator>.
+
+#include <ranges>
+
+#include "test_iterators.h"
+#include "test_macros.h"
+#include "../types.h"
+
+template<class T>
+struct ForwardView : std::ranges::view_base {
+ friend forward_iterator<T*> begin(ForwardView&) { return forward_iterator<T*>(nullptr); }
+ friend forward_iterator<T*> begin(ForwardView const&) { return forward_iterator<T*>(nullptr); }
+ friend forward_iterator<T*> end(ForwardView&) { return forward_iterator<T*>(nullptr); }
+ friend forward_iterator<T*> end(ForwardView const&) { return forward_iterator<T*>(nullptr); }
+};
+
+template<class T>
+struct InputView : std::ranges::view_base {
+ friend cpp17_input_iterator<T*> begin(InputView&) { return cpp17_input_iterator<T*>(nullptr); }
+ friend cpp17_input_iterator<T*> begin(InputView const&) { return cpp17_input_iterator<T*>(nullptr); }
+ friend cpp17_input_iterator<T*> end(InputView&) { return cpp17_input_iterator<T*>(nullptr); }
+ friend cpp17_input_iterator<T*> end(InputView const&) { return cpp17_input_iterator<T*>(nullptr); }
+};
+
+template<class T>
+concept HasIterCategory = requires { typename T::iterator_category; };
+
+void test() {
+ {
+ int buffer[4][4];
+ std::ranges::join_view jv(buffer);
+ using Iter = std::ranges::iterator_t<decltype(jv)>;
+
+ ASSERT_SAME_TYPE(Iter::iterator_concept, std::bidirectional_iterator_tag);
+ ASSERT_SAME_TYPE(Iter::iterator_category, std::bidirectional_iterator_tag);
+ ASSERT_SAME_TYPE(Iter::
diff erence_type, std::ptr
diff _t);
+ ASSERT_SAME_TYPE(Iter::value_type, int);
+ }
+ {
+ using Iter = std::ranges::iterator_t<std::ranges::join_view<ForwardView<ForwardView<int>>>>;
+
+ ASSERT_SAME_TYPE(Iter::iterator_concept, std::forward_iterator_tag);
+ ASSERT_SAME_TYPE(Iter::iterator_category, std::forward_iterator_tag);
+ ASSERT_SAME_TYPE(Iter::
diff erence_type, std::ptr
diff _t);
+ ASSERT_SAME_TYPE(Iter::value_type, int);
+ }
+ {
+ using Iter = std::ranges::iterator_t<std::ranges::join_view<InputView<InputView<int>>>>;
+
+ ASSERT_SAME_TYPE(Iter::iterator_concept, std::input_iterator_tag);
+ static_assert(!HasIterCategory<Iter>);
+ ASSERT_SAME_TYPE(Iter::
diff erence_type, std::ptr
diff _t);
+ ASSERT_SAME_TYPE(Iter::value_type, int);
+ }
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/star.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/star.pass.cpp
new file mode 100644
index 0000000000000..542c3309d59b6
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/star.pass.cpp
@@ -0,0 +1,55 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// constexpr decltype(auto) operator*() const;
+
+#include <cassert>
+#include <ranges>
+
+#include "test_macros.h"
+#include "../types.h"
+
+constexpr bool test() {
+ int buffer[4][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 16}};
+
+ {
+ std::ranges::join_view jv(buffer);
+ auto iter = jv.begin();
+ for (int i = 1; i < 17; ++i) {
+ assert(*iter++ == i);
+ }
+ }
+ {
+ std::ranges::join_view jv(buffer);
+ auto iter = std::next(jv.begin(), 15);
+ assert(*iter++ == 16);
+ assert(iter == jv.end());
+ }
+ {
+ ChildView children[4] = {ChildView(buffer[0]), ChildView(buffer[1]), ChildView(buffer[2]), ChildView(buffer[3])};
+ auto jv = std::ranges::join_view(ParentView(children));
+ auto iter = jv.begin();
+ for (int i = 1; i < 17; ++i) {
+ assert(*iter == i);
+ ++iter;
+ }
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/sentinel/ctor.default.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/sentinel/ctor.default.pass.cpp
new file mode 100644
index 0000000000000..74ab5c9af5a59
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/sentinel/ctor.default.pass.cpp
@@ -0,0 +1,33 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// sentinel() = default;
+
+#include <cassert>
+#include <ranges>
+
+#include "test_macros.h"
+#include "../types.h"
+
+constexpr bool test() {
+ std::ranges::sentinel_t<std::ranges::join_view<CopyableParent>> sent;
+ (void) sent;
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/sentinel/ctor.other.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/sentinel/ctor.other.pass.cpp
new file mode 100644
index 0000000000000..fae2edd53fb87
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/sentinel/ctor.other.pass.cpp
@@ -0,0 +1,41 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// constexpr sentinel(sentinel<!Const> s);
+
+#include <cassert>
+#include <ranges>
+
+#include "test_macros.h"
+#include "../types.h"
+
+constexpr bool test() {
+ int buffer[4][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 16}};
+
+ CopyableChild children[4] = {CopyableChild(buffer[0]), CopyableChild(buffer[1]), CopyableChild(buffer[2]), CopyableChild(buffer[3])};
+ std::ranges::join_view jv(CopyableParent{children});
+ auto sent1 = jv.end();
+ std::ranges::sentinel_t<const decltype(jv)> sent2 = sent1;
+ (void) sent2; // We can't really do anything with these sentinels now :/
+
+ // We cannot create a non-const iterator from a const iterator.
+ static_assert(!std::constructible_from<decltype(sent1), decltype(sent2)>);
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/sentinel/ctor.parent.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/sentinel/ctor.parent.pass.cpp
new file mode 100644
index 0000000000000..fc813dbe563de
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/sentinel/ctor.parent.pass.cpp
@@ -0,0 +1,45 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// constexpr explicit sentinel(Parent& parent);
+
+#include <cassert>
+#include <ranges>
+
+#include "test_macros.h"
+#include "../types.h"
+
+constexpr bool test() {
+ int buffer[4][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 16}};
+
+ CopyableChild children[4] = {CopyableChild(buffer[0]), CopyableChild(buffer[1]), CopyableChild(buffer[2]), CopyableChild(buffer[3])};
+ CopyableParent parent{children};
+ std::ranges::join_view jv(parent);
+ std::ranges::sentinel_t<decltype(jv)> sent(jv);
+ assert(sent == std::ranges::next(jv.begin(), 16));
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+
+ {
+ // Test explicitness.
+ using Parent = std::ranges::join_view<ParentView<ChildView>>;
+ static_assert( std::is_constructible_v<std::ranges::sentinel_t<Parent>, Parent&>);
+ static_assert(!std::is_convertible_v<std::ranges::sentinel_t<Parent>, Parent&>);
+ }
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/sentinel/eq.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/sentinel/eq.pass.cpp
new file mode 100644
index 0000000000000..b33d13ff2df1e
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/sentinel/eq.pass.cpp
@@ -0,0 +1,52 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// template<bool OtherConst>
+// requires sentinel_for<sentinel_t<Base>, iterator_t<maybe-const<OtherConst, V>>>
+// friend constexpr bool operator==(const iterator<OtherConst>& x, const sentinel& y);
+
+#include <cassert>
+#include <ranges>
+
+#include "test_macros.h"
+#include "../types.h"
+
+constexpr bool test() {
+ int buffer[4][4] = {{1111, 2222, 3333, 4444}, {555, 666, 777, 888}, {99, 1010, 1111, 1212}, {13, 14, 15, 16}};
+
+ {
+ ChildView children[4] = {ChildView(buffer[0]), ChildView(buffer[1]), ChildView(buffer[2]), ChildView(buffer[3])};
+ auto jv = std::ranges::join_view(ParentView(children));
+ assert(jv.end() == std::ranges::next(jv.begin(), 16));
+ }
+ {
+ CopyableChild children[4] = {CopyableChild(buffer[0]), CopyableChild(buffer[1]), CopyableChild(buffer[2]), CopyableChild(buffer[3])};
+ const auto jv = std::ranges::join_view(ParentView(children));
+ assert(jv.end() == std::ranges::next(jv.begin(), 16));
+ }
+ {
+ CopyableChild children[4] = {CopyableChild(buffer[0]), CopyableChild(buffer[1]), CopyableChild(buffer[2]), CopyableChild(buffer[3])};
+ const std::ranges::join_view jvc(CopyableParent{children});
+ std::ranges::join_view jv(CopyableParent{children});
+ assert(jvc.end() == std::ranges::next(jv.begin(), 16));
+ assert( jv.end() == std::ranges::next(jvc.begin(), 16));
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/types.h b/libcxx/test/std/ranges/range.adaptors/range.join.view/types.h
new file mode 100644
index 0000000000000..57f79a57485b3
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/types.h
@@ -0,0 +1,141 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_JOIN_TYPES_H
+#define TEST_STD_RANGES_RANGE_ADAPTORS_RANGE_JOIN_TYPES_H
+
+#include <concepts>
+
+#include "test_macros.h"
+#include "test_iterators.h"
+
+int globalBuffer[4][4] = {{1111, 2222, 3333, 4444}, {555, 666, 777, 888}, {99, 1010, 1111, 1212}, {13, 14, 15, 16}};
+
+struct ChildView : std::ranges::view_base {
+ int *ptr_;
+
+ constexpr ChildView(int *ptr = globalBuffer[0]) : ptr_(ptr) {}
+ ChildView(const ChildView&) = delete;
+ ChildView(ChildView&&) = default;
+ ChildView& operator=(const ChildView&) = delete;
+ ChildView& operator=(ChildView&&) = default;
+
+ constexpr cpp20_input_iterator<int *> begin() { return cpp20_input_iterator<int *>(ptr_); }
+ constexpr cpp20_input_iterator<const int *> begin() const { return cpp20_input_iterator<const int *>(ptr_); }
+ constexpr int *end() { return ptr_ + 4; }
+ constexpr const int *end() const { return ptr_ + 4; }
+};
+
+constexpr bool operator==(const cpp20_input_iterator<int*> &lhs, int* rhs) { return lhs.base() == rhs; }
+constexpr bool operator==(int* lhs, const cpp20_input_iterator<int*> &rhs) { return rhs.base() == lhs; }
+
+ChildView globalChildren[4] = {ChildView(globalBuffer[0]), ChildView(globalBuffer[1]), ChildView(globalBuffer[2]), ChildView(globalBuffer[3])};
+
+template<class T>
+struct ParentView : std::ranges::view_base {
+ T *ptr_;
+ unsigned size_;
+
+ constexpr ParentView(T *ptr, unsigned size = 4)
+ : ptr_(ptr), size_(size) {}
+ constexpr ParentView(ChildView *ptr = globalChildren, unsigned size = 4)
+ requires std::same_as<ChildView, T>
+ : ptr_(ptr), size_(size) {}
+ ParentView(const ParentView&) = delete;
+ ParentView(ParentView&&) = default;
+ ParentView& operator=(const ParentView&) = delete;
+ ParentView& operator=(ParentView&&) = default;
+
+ constexpr cpp20_input_iterator<T *> begin() { return cpp20_input_iterator<T *>(ptr_); }
+ constexpr cpp20_input_iterator<const T *> begin() const { return cpp20_input_iterator<const T *>(ptr_); }
+ constexpr T *end() { return ptr_ + size_; }
+ constexpr const T *end() const { return ptr_ + size_; }
+};
+
+template<class T>
+constexpr bool operator==(const cpp20_input_iterator<T*> &lhs, T *rhs) { return lhs.base() == rhs; }
+template<class T>
+constexpr bool operator==(T *lhs, const cpp20_input_iterator<T*> &rhs) { return rhs.base() == lhs; }
+
+struct CopyableChild : std::ranges::view_base {
+ int *ptr_;
+ unsigned size_;
+ constexpr CopyableChild(int *ptr = globalBuffer[0], unsigned size = 4)
+ : ptr_(ptr), size_(size) {}
+
+ constexpr cpp17_input_iterator<int *> begin() { return cpp17_input_iterator<int *>(ptr_); }
+ constexpr cpp17_input_iterator<const int *> begin() const { return cpp17_input_iterator<const int *>(ptr_); }
+ constexpr int *end() { return ptr_ + size_; }
+ constexpr const int *end() const { return ptr_ + size_; }
+};
+
+constexpr bool operator==(const cpp17_input_iterator<const int*> &lhs, const int* rhs) { return lhs.base() == rhs; }
+constexpr bool operator==(const int* lhs, const cpp17_input_iterator<const int*> &rhs) { return rhs.base() == lhs; }
+
+struct CopyableParent : std::ranges::view_base {
+ CopyableChild *ptr_;
+ constexpr CopyableParent(CopyableChild *ptr) : ptr_(ptr) {}
+
+ constexpr cpp17_input_iterator<CopyableChild *> begin() { return cpp17_input_iterator<CopyableChild *>(ptr_); }
+ constexpr cpp17_input_iterator<const CopyableChild *> begin() const { return cpp17_input_iterator<const CopyableChild *>(ptr_); }
+ constexpr CopyableChild *end() { return ptr_ + 4; }
+ constexpr const CopyableChild *end() const { return ptr_ + 4; }
+};
+
+constexpr bool operator==(const cpp17_input_iterator<const CopyableChild*> &lhs, const CopyableChild *rhs) { return lhs.base() == rhs; }
+constexpr bool operator==(const CopyableChild *lhs, const cpp17_input_iterator<const CopyableChild*> &rhs) { return rhs.base() == lhs; }
+
+struct Box { int x; };
+
+template<class T>
+struct InputValueIter {
+ typedef std::input_iterator_tag iterator_category;
+ typedef T value_type;
+ typedef int
diff erence_type;
+ typedef T reference;
+
+ T *ptr_;
+ constexpr InputValueIter(T *ptr) : ptr_(ptr) {}
+
+ constexpr T operator*() const { return std::move(*ptr_); }
+ constexpr void operator++(int) { ++ptr_; }
+ constexpr InputValueIter& operator++() { ++ptr_; return *this; }
+
+ constexpr T *operator->() { return ptr_; }
+};
+
+template<class T>
+constexpr bool operator==(const InputValueIter<T> &lhs, const T* rhs) { return lhs.ptr_ == rhs; }
+template<class T>
+constexpr bool operator==(const T* lhs, const InputValueIter<T> &rhs) { return rhs.ptr_ == lhs; }
+
+template<class T>
+struct ValueView : std::ranges::view_base {
+ InputValueIter<T> ptr_;
+
+ constexpr ValueView(T *ptr) : ptr_(ptr) {}
+
+ constexpr ValueView(ValueView &&other)
+ : ptr_(other.ptr_) { other.ptr_.ptr_ = nullptr; }
+
+ constexpr ValueView& operator=(ValueView &&other) {
+ ptr_ = other.ptr_;
+ other.ptr_ = InputValueIter<T>(nullptr);
+ return *this;
+ }
+
+ ValueView(const ValueView&) = delete;
+ ValueView& operator=(const ValueView&) = delete;
+
+ constexpr InputValueIter<T> begin() { return ptr_; }
+ constexpr const InputValueIter<T> begin() const { return ptr_; }
+ constexpr T *end() { return ptr_.ptr_ + 4; }
+ constexpr const T *end() const { return ptr_.ptr_ + 4; }
+};
+
+#endif // TEST_STD_RANGES_RANGE_ADAPTORS_RANGE_JOIN_TYPES_H
diff --git a/libcxx/test/support/test_iterators.h b/libcxx/test/support/test_iterators.h
index 12cdd36632c12..2381fb8607b40 100644
--- a/libcxx/test/support/test_iterators.h
+++ b/libcxx/test/support/test_iterators.h
@@ -914,6 +914,13 @@ class stride_counting_iterator {
diff erence_type stride_displacement_ = 0;
};
+template<class T, class U>
+concept sentinel_for_base = requires(U const& u) {
+ u.base();
+ requires std::input_or_output_iterator<std::remove_cvref_t<decltype(u.base())>>;
+ requires std::equality_comparable_with<T, decltype(u.base())>;
+};
+
template <std::input_or_output_iterator I>
class sentinel_wrapper {
public:
@@ -927,6 +934,12 @@ class sentinel_wrapper {
constexpr const I& base() const& { return base_; }
constexpr I base() && { return std::move(base_); }
+ template<std::input_or_output_iterator I2>
+ requires sentinel_for_base<I, I2>
+ constexpr bool operator==(I2 const& other) const {
+ return base_ == other.base();
+ }
+
private:
I base_ = I();
};
diff --git a/libcxx/test/support/test_range.h b/libcxx/test/support/test_range.h
index c99e3f72e8bf9..6b279e21ce38e 100644
--- a/libcxx/test/support/test_range.h
+++ b/libcxx/test/support/test_range.h
@@ -62,4 +62,10 @@ struct test_view : std::ranges::view_base {
sentinel end() const;
};
+template<template<class...> class I, class R>
+constexpr auto make_archetype_range(R&& r) {
+ return std::ranges::subrange(I(std::ranges::begin(r)), sentinel_wrapper(std::ranges::end(r)));
+}
+
+
#endif // LIBCXX_TEST_SUPPORT_TEST_RANGE_H
More information about the libcxx-commits
mailing list