diff --git a/libcxx/docs/Status/RangesPaper.csv b/libcxx/docs/Status/RangesPaper.csv --- a/libcxx/docs/Status/RangesPaper.csv +++ b/libcxx/docs/Status/RangesPaper.csv @@ -155,7 +155,7 @@ `[range.transform] `_,`transform_view `_,[range.all],Zoe Carver,✅ `[range.take] `_,`take_view `_,[range.all],Zoe Carver,✅ `[range.join] `_,`join_view `_,[range.all],Zoe Carver,✅ -`[range.split] `_,`split_view (renamed to lazy_split_view by P2210) `_,[range.all],Zoe Carver,In progress +`[range.split] `_,`split_view (renamed to lazy_split_view by P2210) `_,[range.all],Zoe Carver and Konstantin Varlamov,✅ `[range.counted] `_,`view::counted `_,[range.subrange],Zoe Carver,✅ `[range.common] `_,`common_view `_,[range.all],Zoe Carver,✅ `[range.reverse] `_,`reverse_view `_,[range.all],Zoe Carver,✅ diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -373,6 +373,7 @@ __ranges/enable_view.h __ranges/iota_view.h __ranges/join_view.h + __ranges/lazy_split_view.h __ranges/non_propagating_cache.h __ranges/owning_view.h __ranges/range_adaptor.h diff --git a/libcxx/include/__ranges/lazy_split_view.h b/libcxx/include/__ranges/lazy_split_view.h new file mode 100644 --- /dev/null +++ b/libcxx/include/__ranges/lazy_split_view.h @@ -0,0 +1,466 @@ +// -*- 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_LAZY_SPLIT_VIEW_H +#define _LIBCPP___RANGES_LAZY_SPLIT_VIEW_H + +#include <__algorithm/in_in_result.h> +#include <__algorithm/ranges_find.h> +#include <__algorithm/ranges_mismatch.h> +#include <__concepts/constructible.h> +#include <__concepts/convertible_to.h> +#include <__concepts/derived_from.h> +#include <__config> +#include <__functional/bind_back.h> +#include <__functional/ranges_operations.h> +#include <__iterator/concepts.h> +#include <__iterator/default_sentinel.h> +#include <__iterator/incrementable_traits.h> +#include <__iterator/indirectly_comparable.h> +#include <__iterator/iter_move.h> +#include <__iterator/iter_swap.h> +#include <__iterator/iterator_traits.h> +#include <__memory/addressof.h> +#include <__ranges/access.h> +#include <__ranges/all.h> +#include <__ranges/concepts.h> +#include <__ranges/non_propagating_cache.h> +#include <__ranges/range_adaptor.h> +#include <__ranges/single_view.h> +#include <__ranges/subrange.h> +#include <__ranges/view_interface.h> +#include <__utility/forward.h> +#include <__utility/move.h> +#include + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_BEGIN_NAMESPACE_STD + +#if _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_INCOMPLETE_RANGES) + +namespace ranges { + +template struct __require_constant; + +template +concept __tiny_range = + sized_range<_Range> && + requires { typename __require_constant::size()>; } && + (remove_reference_t<_Range>::size() <= 1); + +template + requires view<_View> && view<_Pattern> && + indirectly_comparable, iterator_t<_Pattern>, ranges::equal_to> && + (forward_range<_View> || __tiny_range<_Pattern>) +class lazy_split_view : public view_interface> { + + _LIBCPP_NO_UNIQUE_ADDRESS _View __base_ = _View(); + _LIBCPP_NO_UNIQUE_ADDRESS _Pattern __pattern_ = _Pattern(); + + using _MaybeCurrent = _If, __non_propagating_cache>, __empty_cache>; + _LIBCPP_NO_UNIQUE_ADDRESS _MaybeCurrent __current_ = _MaybeCurrent(); + + template struct __outer_iterator; + template struct __inner_iterator; + +public: + _LIBCPP_HIDE_FROM_ABI + lazy_split_view() + requires default_initializable<_View> && default_initializable<_Pattern> = default; + + _LIBCPP_HIDE_FROM_ABI + constexpr lazy_split_view(_View __base, _Pattern __pattern) + : __base_(std::move(__base)), __pattern_(std::move(__pattern)) {} + + template + requires constructible_from<_View, views::all_t<_Range>> && + constructible_from<_Pattern, single_view>> + _LIBCPP_HIDE_FROM_ABI + constexpr lazy_split_view(_Range&& __r, range_value_t<_Range> __e) + : __base_(views::all(std::forward<_Range>(__r))) + // TODO(varconst): use `views::single` once it's implemented. + , __pattern_(ranges::single_view(std::move(__e))) {} + + _LIBCPP_HIDE_FROM_ABI + constexpr _View base() const& requires copy_constructible<_View> { return __base_; } + _LIBCPP_HIDE_FROM_ABI + constexpr _View base() && { return std::move(__base_); } + + _LIBCPP_HIDE_FROM_ABI + constexpr auto begin() { + if constexpr (forward_range<_View>) { + return __outer_iterator<__simple_view<_View> && __simple_view<_Pattern>>{*this, ranges::begin(__base_)}; + } else { + __current_.__emplace(ranges::begin(__base_)); + return __outer_iterator{*this}; + } + } + + _LIBCPP_HIDE_FROM_ABI + constexpr auto begin() const requires forward_range<_View> && forward_range { + return __outer_iterator{*this, ranges::begin(__base_)}; + } + + _LIBCPP_HIDE_FROM_ABI + constexpr auto end() requires forward_range<_View> && common_range<_View> { + return __outer_iterator<__simple_view<_View> && __simple_view<_Pattern>>{*this, ranges::end(__base_)}; + } + + _LIBCPP_HIDE_FROM_ABI + constexpr auto end() const { + if constexpr (forward_range<_View> && forward_range && common_range) { + return __outer_iterator{*this, ranges::end(__base_)}; + } else { + return default_sentinel; + } + } + +private: + + template + struct __outer_iterator_category {}; + + template + struct __outer_iterator_category<_Tp> { + using iterator_category = input_iterator_tag; + }; + + template + struct __outer_iterator : __outer_iterator_category<__maybe_const<_Const, _View>> { + private: + template + friend struct __inner_iterator; + friend __outer_iterator; + + using _Parent = __maybe_const<_Const, lazy_split_view>; + using _Base = __maybe_const<_Const, _View>; + + _Parent* __parent_ = nullptr; + using _MaybeCurrent = _If, iterator_t<_Base>, __empty_cache>; + _LIBCPP_NO_UNIQUE_ADDRESS _MaybeCurrent __current_ = _MaybeCurrent(); + bool __trailing_empty_ = false; + + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI + constexpr auto& __current() noexcept { + if constexpr (forward_range<_View>) { + return __current_; + } else { + return *__parent_->__current_; + } + } + + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI + constexpr const auto& __current() const noexcept { + if constexpr (forward_range<_View>) { + return __current_; + } else { + return *__parent_->__current_; + } + } + + // Workaround for the GCC issue that doesn't allow calling `__parent_->__base_` from friend functions (because + // `__base_` is private). + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI + constexpr auto& __parent_base() const noexcept { + return __parent_->__base_; + } + + public: + // using iterator_category = inherited; + using iterator_concept = conditional_t, forward_iterator_tag, input_iterator_tag>; + using difference_type = range_difference_t<_Base>; + + struct value_type : view_interface { + private: + __outer_iterator __i_ = __outer_iterator(); + + public: + _LIBCPP_HIDE_FROM_ABI + value_type() = default; + _LIBCPP_HIDE_FROM_ABI + constexpr explicit value_type(__outer_iterator __i) + : __i_(std::move(__i)) {} + + _LIBCPP_HIDE_FROM_ABI + constexpr __inner_iterator<_Const> begin() const { return __inner_iterator<_Const>{__i_}; } + _LIBCPP_HIDE_FROM_ABI + constexpr default_sentinel_t end() const noexcept { return default_sentinel; } + }; + + _LIBCPP_HIDE_FROM_ABI + __outer_iterator() = default; + + _LIBCPP_HIDE_FROM_ABI + constexpr explicit __outer_iterator(_Parent& __parent) + requires (!forward_range<_Base>) + : __parent_(std::addressof(__parent)) {} + + _LIBCPP_HIDE_FROM_ABI + constexpr __outer_iterator(_Parent& __parent, iterator_t<_Base> __current) + requires forward_range<_Base> + : __parent_(std::addressof(__parent)), __current_(std::move(__current)) {} + + _LIBCPP_HIDE_FROM_ABI + constexpr __outer_iterator(__outer_iterator __i) + requires _Const && convertible_to, iterator_t<_Base>> + : __parent_(__i.__parent_), __current_(std::move(__i.__current_)) {} + + _LIBCPP_HIDE_FROM_ABI + constexpr value_type operator*() const { return value_type{*this}; } + + _LIBCPP_HIDE_FROM_ABI + constexpr __outer_iterator& operator++() { + const auto __end = ranges::end(__parent_->__base_); + if (__current() == __end) { + __trailing_empty_ = false; + return *this; + } + + const auto [__pbegin, __pend] = ranges::subrange{__parent_->__pattern_}; + if (__pbegin == __pend) { + // Empty pattern: split on every element in the input range + ++__current(); + + } else if constexpr (__tiny_range<_Pattern>) { + // One-element pattern: we can use `ranges::find`. + __current() = ranges::find(std::move(__current()), __end, *__pbegin); + if (__current() != __end) { + // Make sure we point to after the separator we just found. + ++__current(); + if (__current() == __end) + __trailing_empty_ = true; + } + + } else { + // General case for n-element pattern. + do { + const auto [__b, __p] = ranges::mismatch(__current(), __end, __pbegin, __pend); + if (__p == __pend) { + __current() = __b; + if (__current() == __end) { + __trailing_empty_ = true; + } + break; // The pattern matched; skip it. + } + } while (++__current() != __end); + } + + return *this; + } + + _LIBCPP_HIDE_FROM_ABI + constexpr decltype(auto) operator++(int) { + if constexpr (forward_range<_Base>) { + auto __tmp = *this; + ++*this; + return __tmp; + + } else { + ++*this; + } + } + + _LIBCPP_HIDE_FROM_ABI + friend constexpr bool operator==(const __outer_iterator& __x, const __outer_iterator& __y) + requires forward_range<_Base> { + return __x.__current_ == __y.__current_ && __x.__trailing_empty_ == __y.__trailing_empty_; + } + + _LIBCPP_HIDE_FROM_ABI + friend constexpr bool operator==(const __outer_iterator& __x, default_sentinel_t) { + _LIBCPP_ASSERT(__x.__parent_, "Cannot call comparison on a default-constructed iterator."); + return __x.__current() == ranges::end(__x.__parent_base()) && !__x.__trailing_empty_; + } + }; + + template + struct __inner_iterator_category {}; + + template + struct __inner_iterator_category<_Tp> { + using iterator_category = _If< + derived_from>::iterator_category, forward_iterator_tag>, + forward_iterator_tag, + typename iterator_traits>::iterator_category + >; + }; + + template + struct __inner_iterator : __inner_iterator_category<__maybe_const<_Const, _View>> { + private: + using _Base = __maybe_const<_Const, _View>; + // Workaround for a GCC issue. + static constexpr bool _OuterConst = _Const; + __outer_iterator<_Const> __i_ = __outer_iterator<_OuterConst>(); + bool __incremented_ = false; + + // Note: these private functions are necessary because GCC doesn't allow calls to private members of `__i_` from + // free functions that are friends of `inner-iterator`. + + _LIBCPP_HIDE_FROM_ABI + constexpr bool __is_done() const { + _LIBCPP_ASSERT(__i_.__parent_, "Cannot call comparison on a default-constructed iterator."); + + auto [__pcur, __pend] = ranges::subrange{__i_.__parent_->__pattern_}; + auto __end = ranges::end(__i_.__parent_->__base_); + + if constexpr (__tiny_range<_Pattern>) { + const auto& __cur = __i_.__current(); + if (__cur == __end) + return true; + if (__pcur == __pend) + return __incremented_; + + return *__cur == *__pcur; + + } else { + auto __cur = __i_.__current(); + if (__cur == __end) + return true; + if (__pcur == __pend) + return __incremented_; + + do { + if (*__cur != *__pcur) + return false; + if (++__pcur == __pend) + return true; + } while (++__cur != __end); + + return false; + } + } + + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI + constexpr auto& __outer_current() noexcept { + return __i_.__current(); + } + + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI + constexpr const auto& __outer_current() const noexcept { + return __i_.__current(); + } + + public: + // using iterator_category = inherited; + using iterator_concept = typename __outer_iterator<_Const>::iterator_concept; + using value_type = range_value_t<_Base>; + using difference_type = range_difference_t<_Base>; + + _LIBCPP_HIDE_FROM_ABI + __inner_iterator() = default; + + _LIBCPP_HIDE_FROM_ABI + constexpr explicit __inner_iterator(__outer_iterator<_Const> __i) + : __i_(std::move(__i)) {} + + _LIBCPP_HIDE_FROM_ABI + constexpr const iterator_t<_Base>& base() const& noexcept { return __i_.__current(); } + _LIBCPP_HIDE_FROM_ABI + constexpr iterator_t<_Base> base() && + requires forward_range<_View> { return std::move(__i_.__current()); } + + _LIBCPP_HIDE_FROM_ABI + constexpr decltype(auto) operator*() const { return *__i_.__current(); } + + _LIBCPP_HIDE_FROM_ABI + constexpr __inner_iterator& operator++() { + __incremented_ = true; + + if constexpr (!forward_range<_Base>) { + if constexpr (_Pattern::size() == 0) { + return *this; + } + } + + ++__i_.__current(); + return *this; + } + + _LIBCPP_HIDE_FROM_ABI + constexpr decltype(auto) operator++(int) { + if constexpr (forward_range<_Base>) { + auto __tmp = *this; + ++*this; + return __tmp; + + } else { + ++*this; + } + } + + _LIBCPP_HIDE_FROM_ABI + friend constexpr bool operator==(const __inner_iterator& __x, const __inner_iterator& __y) + requires forward_range<_Base> { + return __x.__outer_current() == __y.__outer_current(); + } + + _LIBCPP_HIDE_FROM_ABI + friend constexpr bool operator==(const __inner_iterator& __x, default_sentinel_t) { + return __x.__is_done(); + } + + _LIBCPP_HIDE_FROM_ABI + friend constexpr decltype(auto) iter_move(const __inner_iterator& __i) + noexcept(noexcept(ranges::iter_move(__i.__outer_current()))) { + return ranges::iter_move(__i.__outer_current()); + } + + _LIBCPP_HIDE_FROM_ABI + friend constexpr void iter_swap(const __inner_iterator& __x, const __inner_iterator& __y) + noexcept(noexcept(ranges::iter_swap(__x.__outer_current(), __y.__outer_current()))) + requires indirectly_swappable> { + ranges::iter_swap(__x.__outer_current(), __y.__outer_current()); + } + }; + +}; + +template +lazy_split_view(_Range&&, _Pattern&&) -> lazy_split_view, views::all_t<_Pattern>>; + +template +lazy_split_view(_Range&&, range_value_t<_Range>) + -> lazy_split_view, single_view>>; + +namespace views { +namespace __lazy_split_view { +struct __fn : __range_adaptor_closure<__fn> { + template + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI + constexpr auto operator()(_Range&& __range, _Pattern&& __pattern) const + noexcept(noexcept(lazy_split_view(std::forward<_Range>(__range), std::forward<_Pattern>(__pattern)))) + -> decltype( lazy_split_view(std::forward<_Range>(__range), std::forward<_Pattern>(__pattern))) + { return lazy_split_view(std::forward<_Range>(__range), std::forward<_Pattern>(__pattern)); } + + template + requires constructible_from, _Pattern> + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI + constexpr auto operator()(_Pattern&& __pattern) const + noexcept(is_nothrow_constructible_v, _Pattern>) { + return __range_adaptor_closure_t(std::__bind_back(*this, std::forward<_Pattern>(__pattern))); + } +}; +} // namespace __lazy_split_view + +inline namespace __cpo { + inline constexpr auto lazy_split = __lazy_split_view::__fn{}; +} // namespace __cpo +} // namespace views + +} // namespace ranges + +#endif // _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_INCOMPLETE_RANGES) + +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP___RANGES_LAZY_SPLIT_VIEW_H diff --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap --- a/libcxx/include/module.modulemap +++ b/libcxx/include/module.modulemap @@ -851,6 +851,7 @@ 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 lazy_split_view { private header "__ranges/lazy_split_view.h" } module non_propagating_cache { private header "__ranges/non_propagating_cache.h" } module owning_view { private header "__ranges/owning_view.h" } module range_adaptor { private header "__ranges/range_adaptor.h" } diff --git a/libcxx/include/ranges b/libcxx/include/ranges --- a/libcxx/include/ranges +++ b/libcxx/include/ranges @@ -204,6 +204,20 @@ template requires view && input_range> class join_view; + + // [range.lazy.split], lazy split view + template + concept tiny-range = see below; // exposition only + + template + requires view && view && + indirectly_comparable, iterator_t, ranges::equal_to> && + (forward_range || tiny-range) + class lazy_split_view; + + namespace views { + inline constexpr unspecified lazy_split = unspecified; + } } namespace std { @@ -254,6 +268,7 @@ #include <__ranges/enable_view.h> #include <__ranges/iota_view.h> #include <__ranges/join_view.h> +#include <__ranges/lazy_split_view.h> #include <__ranges/rbegin.h> #include <__ranges/ref_view.h> #include <__ranges/rend.h> diff --git a/libcxx/test/libcxx/lint/lint_headers.sh.py b/libcxx/test/libcxx/lint/lint_headers.sh.py old mode 100644 new mode 100755 diff --git a/libcxx/test/libcxx/private_headers.verify.cpp b/libcxx/test/libcxx/private_headers.verify.cpp --- a/libcxx/test/libcxx/private_headers.verify.cpp +++ b/libcxx/test/libcxx/private_headers.verify.cpp @@ -404,6 +404,7 @@ #include <__ranges/enable_view.h> // expected-error@*:* {{use of private header from outside its module: '__ranges/enable_view.h'}} #include <__ranges/iota_view.h> // expected-error@*:* {{use of private header from outside its module: '__ranges/iota_view.h'}} #include <__ranges/join_view.h> // expected-error@*:* {{use of private header from outside its module: '__ranges/join_view.h'}} +#include <__ranges/lazy_split_view.h> // expected-error@*:* {{use of private header from outside its module: '__ranges/lazy_split_view.h'}} #include <__ranges/non_propagating_cache.h> // expected-error@*:* {{use of private header from outside its module: '__ranges/non_propagating_cache.h'}} #include <__ranges/owning_view.h> // expected-error@*:* {{use of private header from outside its module: '__ranges/owning_view.h'}} #include <__ranges/range_adaptor.h> // expected-error@*:* {{use of private header from outside its module: '__ranges/range_adaptor.h'}} diff --git a/libcxx/test/libcxx/ranges/range.adaptors/range.lazy.split/no_unique_address.compile.pass.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.lazy.split/no_unique_address.compile.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/ranges/range.adaptors/range.lazy.split/no_unique_address.compile.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-has-no-incomplete-ranges + +// clang-cl and cl currently don't support [[no_unique_address]] +// XFAIL: msvc + +// class lazy_split_view { +// _LIBCPP_NO_UNIQUE_ADDRESS _View __base_ = _View(); +// _LIBCPP_NO_UNIQUE_ADDRESS _Pattern __pattern_ = _Pattern(); +// }; + +#include + +#include +#include "test_iterators.h" + +// Verify the optimization that, if `View` is a forward range, the `lazy_split_view` itself doesn't store the `current` +// iterator (instead, it's stored in the `outer-iterator`). +// +// Note that the Standard marks all data members of `lazy_split_view` as "exposition only", so this test has to be +// libc++-specific. +namespace test1 { + + using SplitView = std::ranges::lazy_split_view; + // The `lazy_split_view` only stores the `View` and the `Pattern`, not an iterator. + static_assert(sizeof(SplitView) == sizeof(std::string_view) * 2); + +} // namespace test1 + +// Verify the optimization that, if `View` is an input range, the `outer-iterator` doesn't store the `current` iterator +// (instead, it's stored in the `lazy_split_view` itself). +// +// Note that the Standard marks all data members of `outer-iterator` as "exposition only", so this test has to be +// libc++-specific. +namespace test2 { + + struct InputView : std::ranges::view_base { + int x; + cpp20_input_iterator begin() const; + sentinel_wrapper> end() const; + }; + static_assert( std::ranges::input_range); + static_assert(!std::ranges::forward_range); + static_assert( std::ranges::view); + + struct TinyView : std::ranges::view_base { + int x; + int* begin() const; + int* end() const; + constexpr static size_t size() { return 1; } + }; + static_assert( std::ranges::forward_range); + static_assert( std::ranges::__tiny_range); + static_assert( std::ranges::view); + + using SplitView = std::ranges::lazy_split_view; + using OuterIter = std::ranges::iterator_t; + // The `outer-iterator` only stores a pointer to the parent and a boolean (aligned to the size of a pointer), not an + // iterator. + static_assert(sizeof(OuterIter) == sizeof(void*) * 2); + +} // namespace test2 + +// Verify the libc++-specific optimization that empty `View` and `Pattern` use the `[[no_unique_address]]` attribute. +// Both `View` and `Pattern` are forward views. +namespace test3 { + + struct EmptyView1 : std::ranges::view_base { + int* begin() const; + int* end() const; + }; + static_assert( std::ranges::forward_range); + static_assert( std::ranges::view); + + // Note: it's important to inherit `EmptyView1` and `EmptyView2` from different bases, otherwise they still cannot share + // the same address regardless of whether `[[no_unique_address]]` is used. + struct EmptyView2 : std::ranges::view_interface { + int* begin() const; + int* end() const; + }; + static_assert( std::ranges::forward_range); + static_assert( std::ranges::view); + + static_assert(sizeof(std::ranges::lazy_split_view) == 1); + +} // namespace test3 + +// Verify the libc++-specific optimization that empty `View` and `Pattern` use the `[[no_unique_address]]` attribute. +// `View` is an input view and `Pattern` is a tiny view. +namespace test4 { + + struct EmptyInputView : std::ranges::view_base { + cpp20_input_iterator begin() const; + sentinel_wrapper> end() const; + }; + static_assert( std::ranges::input_range); + static_assert(!std::ranges::forward_range); + static_assert( std::ranges::view); + + struct EmptyTinyView : std::ranges::view_base { + int* begin() const; + int* end() const; + constexpr static size_t size() { return 1; } + }; + static_assert( std::ranges::forward_range); + static_assert( std::ranges::__tiny_range); + static_assert( std::ranges::view); + + static_assert(sizeof(std::ranges::lazy_split_view) == + sizeof(std::ranges::__non_propagating_cache>)); + +} // namespace test4 diff --git a/libcxx/test/libcxx/ranges/range.adaptors/range.lazy.split/range.lazy.split.inner/assert.equal.pass.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.lazy.split/range.lazy.split.inner/assert.equal.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/ranges/range.adaptors/range.lazy.split/range.lazy.split.inner/assert.equal.pass.cpp @@ -0,0 +1,35 @@ +//===----------------------------------------------------------------------===// +// +// 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, windows, libcxx-no-debug-mode +// ADDITIONAL_COMPILE_FLAGS: -D_LIBCPP_ENABLE_ASSERTIONS=1 + +// + +// friend constexpr bool operator==(const inner-iterator& x, default_sentinel_t); +// +// Can't compare a default-constructed `inner-iterator` with the default sentinel. + +#include + +#include "check_assertion.h" +#include "../types.h" + +int main(int, char**) { + { + InnerIterForward i; + TEST_LIBCPP_ASSERT_FAILURE(i == std::default_sentinel, "Cannot call comparison on a default-constructed iterator."); + } + + { + InnerIterInput i; + TEST_LIBCPP_ASSERT_FAILURE(i == std::default_sentinel, "Cannot call comparison on a default-constructed iterator."); + } + + return 0; +} diff --git a/libcxx/test/libcxx/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer/assert.equal.pass.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer/assert.equal.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer/assert.equal.pass.cpp @@ -0,0 +1,35 @@ +//===----------------------------------------------------------------------===// +// +// 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, windows, libcxx-no-debug-mode +// ADDITIONAL_COMPILE_FLAGS: -D_LIBCPP_ENABLE_ASSERTIONS=1 + +// + +// friend constexpr bool operator==(const inner-iterator& x, default_sentinel_t); +// +// Can't compare a default-constructed `inner-iterator` with the default sentinel. + +#include + +#include "check_assertion.h" +#include "../types.h" + +int main(int, char**) { + { + OuterIterForward i; + TEST_LIBCPP_ASSERT_FAILURE(i == std::default_sentinel, "Cannot call comparison on a default-constructed iterator."); + } + + { + OuterIterInput i; + TEST_LIBCPP_ASSERT_FAILURE(i == std::default_sentinel, "Cannot call comparison on a default-constructed iterator."); + } + + return 0; +} diff --git a/libcxx/test/libcxx/ranges/range.adaptors/range.lazy.split/types.h b/libcxx/test/libcxx/ranges/range.adaptors/range.lazy.split/types.h new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/ranges/range.adaptors/range.lazy.split/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_LIBCXX_RANGES_RANGE_ADAPTORS_RANGE_LAZY_SPLIT_TYPES_H +#define TEST_LIBCXX_RANGES_RANGE_ADAPTORS_RANGE_LAZY_SPLIT_TYPES_H + +#include +#include +#include +#include "test_iterators.h" + +// ForwardView + +struct ForwardView : std::ranges::view_base { + constexpr explicit ForwardView() = default; + constexpr ForwardView(ForwardView&&) = default; + constexpr ForwardView& operator=(ForwardView&&) = default; + constexpr forward_iterator begin() const { return forward_iterator(nullptr); } + constexpr forward_iterator end() const { return forward_iterator(nullptr); } +}; +static_assert( std::ranges::forward_range); +static_assert( std::ranges::forward_range); +static_assert( std::ranges::view); +static_assert(!std::is_copy_constructible_v); + +// InputView + +struct InputView : std::ranges::view_base { + constexpr InputView() = default; + + constexpr cpp20_input_iterator begin() { return cpp20_input_iterator(nullptr); } + constexpr sentinel_wrapper> end() { + return sentinel_wrapper(cpp20_input_iterator(nullptr)); + } + constexpr cpp20_input_iterator begin() const { return cpp20_input_iterator(nullptr); } + constexpr sentinel_wrapper> end() const { + return sentinel_wrapper(cpp20_input_iterator(nullptr)); + } +}; + +static_assert(std::ranges::input_range); +static_assert(std::ranges::input_range); +static_assert(std::ranges::view); + +// ForwardTinyView + +struct ForwardTinyView : std::ranges::view_base { + constexpr ForwardTinyView() = default; + constexpr forward_iterator begin() const { return forward_iterator(nullptr); } + constexpr forward_iterator end() const { return forward_iterator(nullptr); } + constexpr static size_t size() { return 1; } +}; +static_assert(std::ranges::forward_range); +static_assert(std::ranges::view); +LIBCPP_STATIC_ASSERT(std::ranges::__tiny_range); + +// Aliases + +using SplitViewForward = std::ranges::lazy_split_view; +using OuterIterForward = std::ranges::iterator_t; +using InnerIterForward = std::ranges::iterator_t; + +using SplitViewInput = std::ranges::lazy_split_view; +using OuterIterInput = std::ranges::iterator_t; +using InnerIterInput = std::ranges::iterator_t; + +#endif // TEST_LIBCXX_RANGES_RANGE_ADAPTORS_RANGE_LAZY_SPLIT_TYPES_H 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 --- 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 @@ -91,8 +91,8 @@ //static_assert(test(std::views::elements<0>, pairs)); //static_assert(test(std::views::filter, a, [](int x){ return x < 10; })); //static_assert(test(std::views::join, arrays)); +static_assert(test(std::views::lazy_split, a, 4)); static_assert(test(std::views::reverse, a)); -//static_assert(test(std::views::split, a, 4)); //static_assert(test(std::views::take, a, 10)); //static_assert(test(std::views::take_while, a, [](int x){ return x < 10; })); static_assert(test(std::views::transform, a, [](int x){ return x + 1; })); diff --git a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/adaptor.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/adaptor.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/adaptor.pass.cpp @@ -0,0 +1,128 @@ +//===----------------------------------------------------------------------===// +// +// 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-has-no-incomplete-ranges + +// std::views::lazy_split + +#include + +#include +#include +#include +#include +#include + +#include "test_iterators.h" +#include "types.h" + +template +concept CanBePiped = requires (View&& view, T&& t) { + { std::forward(view) | std::forward(t) }; +}; + +struct SomeView : std::ranges::view_base { + const std::string_view* v_; + constexpr SomeView(const std::string_view& v) : v_(&v) {} + constexpr auto begin() const { return v_->begin(); } + constexpr auto end() const { return v_->end(); } +}; + +struct NotAView { }; + +static_assert(!std::is_invocable_v); +static_assert(!std::is_invocable_v); +static_assert(!std::is_invocable_v); +static_assert( std::is_invocable_v); + +static_assert( CanBePiped); +static_assert( CanBePiped); +static_assert(!CanBePiped); +static_assert(!CanBePiped); + +static_assert(std::same_as); + +constexpr bool test() { + std::string_view input = "abc"; + std::string_view sep = "a"; + + // Test that `std::views::lazy_split` is a range adaptor. + + // Test `views::lazy_split(input, sep)`. + { + SomeView view(input); + + using Result = std::ranges::lazy_split_view; + std::same_as decltype(auto) result = std::views::lazy_split(view, sep); + assert(result.base().begin() == input.begin()); + assert(result.base().end() == input.end()); + } + + // Test `views::lazy_split(sep)(input)`. + { + SomeView view(input); + + using Result = std::ranges::lazy_split_view; + std::same_as decltype(auto) result = std::views::lazy_split(sep)(view); + assert(result.base().begin() == input.begin()); + assert(result.base().end() == input.end()); + } + + // Test `view | views::lazy_split`. + { + SomeView view(input); + + using Result = std::ranges::lazy_split_view; + std::same_as decltype(auto) result = view | std::views::lazy_split(sep); + assert(result.base().begin() == input.begin()); + assert(result.base().end() == input.end()); + } + + // Test `adaptor | views::lazy_split`. + { + SomeView view(input); + auto f = [](char c) { return c; }; + auto partial = std::views::transform(f) | std::views::lazy_split(sep); + + using Result = std::ranges::lazy_split_view, std::string_view>; + std::same_as decltype(auto) result = partial(view); + assert(result.base().base().begin() == input.begin()); + assert(result.base().base().end() == input.end()); + } + + // Test `views::lazy_split | adaptor`. + { + SomeView view(input); + auto f = [](auto v) { return v; }; + auto partial = std::views::lazy_split(sep) | std::views::transform(f); + + using Result = std::ranges::transform_view, decltype(f)>; + std::same_as decltype(auto) result = partial(view); + assert(result.base().base().begin() == input.begin()); + assert(result.base().base().end() == input.end()); + } + + // Test that one can call `std::views::lazy_split` with arbitrary stuff, as long as we + // don't try to actually complete the call by passing it a range. + // + // That makes no sense and we can't do anything with the result, but it's valid. + { + struct X { }; + [[maybe_unused]] auto partial = std::views::lazy_split(X{}); + } + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/base.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/base.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/base.pass.cpp @@ -0,0 +1,126 @@ +//===----------------------------------------------------------------------===// +// +// 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-has-no-incomplete-ranges + +// constexpr View base() const& requires copy_constructible<_View>; +// constexpr View base() &&; + +#include + +#include +#include +#include +#include "types.h" + +struct MoveOnlyView : std::ranges::view_base { + std::string_view view_; + constexpr MoveOnlyView() = default; + constexpr MoveOnlyView(const char* ptr) : view_(ptr) {} + constexpr MoveOnlyView(std::string_view v) : view_(v) {} + constexpr MoveOnlyView(MoveOnlyView&&) = default; + constexpr MoveOnlyView& operator=(MoveOnlyView&&) = default; + constexpr const char* begin() const { return view_.begin(); } + constexpr const char* end() const { return view_.end(); } + constexpr bool operator==(MoveOnlyView rhs) const { return view_ == rhs.view_; } +}; +static_assert( std::ranges::view); +static_assert( std::ranges::contiguous_range); +static_assert(!std::copyable); + +struct ViewWithInitTracking : std::ranges::view_base { + enum class InitializedBy { + Copy, + Move, + Invalid + }; + + std::string_view v_; + InitializedBy initialized_by = InitializedBy::Invalid; + constexpr ViewWithInitTracking(std::string_view v) : v_(v) {} + + constexpr auto begin() const { return v_.begin(); } + constexpr auto end() const { return v_.end(); } + + constexpr ViewWithInitTracking(const ViewWithInitTracking& rhs) : v_(rhs.v_) { initialized_by = InitializedBy::Copy; } + constexpr ViewWithInitTracking(ViewWithInitTracking&& rhs) : v_(rhs.v_) { initialized_by = InitializedBy::Move; } + constexpr ViewWithInitTracking& operator=(const ViewWithInitTracking& rhs) = default; + constexpr ViewWithInitTracking& operator=(ViewWithInitTracking&& rhs) = default; + constexpr bool operator==(const ViewWithInitTracking& rhs) const { return v_ == rhs.v_; } +}; + +template +concept CanCallBase = requires(View v) { std::forward(v).base(); }; + +static_assert( CanCallBase&&>); +static_assert(!CanCallBase&>); +static_assert(!CanCallBase const &>); +static_assert(!CanCallBase const &&>); + +constexpr bool test() { + using View = ViewWithInitTracking; + + // Copyable input -- both lvalue and rvalue overloads of `base` are available. + { + // Non-const lvalue. + { + View str("abc def"); + std::ranges::lazy_split_view v(str, " "); + + std::same_as decltype(auto) result = v.base(); + assert(result == str); + assert(result.initialized_by == View::InitializedBy::Copy); + } + + // Const lvalue. + { + View str("abc def"); + const std::ranges::lazy_split_view v(str, " "); + + std::same_as decltype(auto) result = v.base(); + assert(result == str); + assert(result.initialized_by == View::InitializedBy::Copy); + } + + // Non-const rvalue. + { + View str("abc def"); + std::ranges::lazy_split_view v(str, " "); + + std::same_as decltype(auto) result = std::move(v).base(); + assert(result == str); + assert(result.initialized_by == View::InitializedBy::Move); + } + + // Const rvalue. + { + View str("abc def"); + const std::ranges::lazy_split_view v(str, " "); + + std::same_as decltype(auto) result = std::move(v).base(); + assert(result == str); + assert(result.initialized_by == View::InitializedBy::Copy); + } + } + + // Move-only input -- only the rvalue overload of `base` is available. + { + std::ranges::lazy_split_view v; + assert(std::move(v).base() == MoveOnlyView()); + } + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/begin.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/begin.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/begin.pass.cpp @@ -0,0 +1,149 @@ +//===----------------------------------------------------------------------===// +// +// 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-has-no-incomplete-ranges + +// constexpr auto begin(); +// constexpr auto begin() const requires forward_range && forward_range; + +#include + +#include +#include +#include "test_iterators.h" +#include "types.h" + +template +concept ConstBeginDisabled = !requires (const View v) { + { (*v.begin()) }; +}; + +constexpr bool test() { + // non-const: forward_range && simple-view -> outer-iterator + // const: forward_range && forward_range -> outer-iterator + { + using V = ForwardView; + using P = V; + + static_assert(std::ranges::forward_range); + static_assert(std::ranges::forward_range); + LIBCPP_STATIC_ASSERT(std::ranges::__simple_view); + LIBCPP_STATIC_ASSERT(std::ranges::__simple_view

); + + { + std::ranges::lazy_split_view v; + auto it = v.begin(); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + } + + { + const std::ranges::lazy_split_view cv; + auto it = cv.begin(); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + } + } + + // non-const: forward_range && !simple-view -> outer-iterator + // const: forward_range && forward_range -> outer-iterator + { + using V = ForwardDiffView; + using P = V; + + static_assert(std::ranges::forward_range); + static_assert(std::ranges::forward_range); + LIBCPP_STATIC_ASSERT(!std::ranges::__simple_view); + LIBCPP_STATIC_ASSERT(!std::ranges::__simple_view

); + + { + std::ranges::lazy_split_view v; + auto it = v.begin(); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + } + + { + const std::ranges::lazy_split_view cv; + auto it = cv.begin(); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + } + } + + // non-const: forward_range && !simple-view -> outer-iterator + // const: forward_range && !forward_range -> disabled + { + using V = ForwardOnlyIfNonConstView; + using P = V; + static_assert(std::ranges::forward_range); + static_assert(!std::ranges::forward_range); + LIBCPP_STATIC_ASSERT(!std::ranges::__simple_view); + LIBCPP_STATIC_ASSERT(!std::ranges::__simple_view

); + + std::ranges::lazy_split_view v; + auto it = v.begin(); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + + static_assert(ConstBeginDisabled); + } + + // non-const: forward_range && simple-view && !simple-view -> outer-iterator + // const: forward_range && forward_range -> outer-iterator + { + using V = ForwardView; + using P = ForwardOnlyIfNonConstView; + + static_assert(std::ranges::forward_range); + static_assert(std::ranges::forward_range); + LIBCPP_STATIC_ASSERT(std::ranges::__simple_view); + LIBCPP_STATIC_ASSERT(!std::ranges::__simple_view

); + + { + std::ranges::lazy_split_view v; + auto it = v.begin(); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + } + + { + const std::ranges::lazy_split_view cv; + auto it = cv.begin(); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + } + } + + // non-const: !forward_range && tiny-range -> outer-iterator + // const: !forward_range -> disabled + { + using V = InputView; + using P = ForwardTinyView; + + static_assert(!std::ranges::forward_range); + static_assert(std::ranges::forward_range

); + + std::ranges::lazy_split_view v; + auto it = v.begin(); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + + static_assert(ConstBeginDisabled); + } + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/constraints.compile.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/constraints.compile.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/constraints.compile.pass.cpp @@ -0,0 +1,210 @@ +//===----------------------------------------------------------------------===// +// +// 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-has-no-incomplete-ranges + +// template +// requires view && view && +// indirectly_comparable, iterator_t, ranges::equal_to> && +// (forward_range || tiny-range) +// class lazy_split_view; + +#include + +#include "test_iterators.h" +#include "types.h" + +struct ForwardRange { + forward_iterator begin() const; + forward_iterator end() const; +}; +static_assert( std::ranges::forward_range); + +template +concept CanInstantiate = requires { + typename std::ranges::lazy_split_view; +}; + +// All constraints satisfied (`View` and `Pattern` are forward views). +namespace test1 { + + using View = ForwardView; + using Pattern = ForwardView; + static_assert( std::ranges::forward_range); + static_assert( std::ranges::forward_range); + static_assert( std::ranges::view); + static_assert( std::ranges::view); + static_assert( std::indirectly_comparable< + std::ranges::iterator_t, std::ranges::iterator_t, std::ranges::equal_to>); + static_assert( CanInstantiate); + +} // namespace test1 + +// All constraints satisfied (`View` is an input view and `Pattern` is a tiny view). +namespace test2 { + + using View = InputView; + using Pattern = ForwardTinyView; + static_assert( std::ranges::input_range); + static_assert( std::ranges::forward_range); + static_assert( std::ranges::view); + static_assert( std::ranges::view); + static_assert( std::indirectly_comparable< + std::ranges::iterator_t, std::ranges::iterator_t, std::ranges::equal_to>); + static_assert( CanInstantiate); + +} // namespace test2 + +// `View` is not an input range. +namespace test3 { + + struct AlmostInputIterator { + using value_type = char; + using difference_type = ptrdiff_t; + using iterator_concept = int; + + constexpr const char& operator*() const; + constexpr AlmostInputIterator& operator++(); + constexpr void operator++(int); + constexpr bool operator==(const AlmostInputIterator&) const; + }; + + static_assert( std::input_or_output_iterator); + static_assert(!std::input_iterator); + + struct NonInputView : std::ranges::view_base { + AlmostInputIterator begin() const; + AlmostInputIterator end() const; + }; + + using View = NonInputView; + using Pattern = ForwardTinyView; + static_assert(!std::ranges::input_range); + static_assert( std::ranges::forward_range); + static_assert( std::ranges::view); + static_assert( std::ranges::view); + static_assert( std::indirectly_comparable< + std::ranges::iterator_t, std::ranges::iterator_t, std::ranges::equal_to>); + static_assert(!CanInstantiate); + +} // namespace test3 + +// `View` is not a view. +namespace test4 { + + using View = ForwardRange; + using Pattern = ForwardView; + static_assert( std::ranges::input_range); + static_assert( std::ranges::forward_range); + static_assert(!std::ranges::view); + static_assert( std::ranges::view); + static_assert( std::indirectly_comparable< + std::ranges::iterator_t, std::ranges::iterator_t, std::ranges::equal_to>); + static_assert(!CanInstantiate); + +} // namespace test4 + +// `Pattern` is not a forward range. +namespace test5 { + + using View = ForwardView; + using Pattern = InputView; + static_assert( std::ranges::input_range); + static_assert(!std::ranges::forward_range); + static_assert( std::ranges::view); + static_assert( std::ranges::view); + static_assert( std::indirectly_comparable< + std::ranges::iterator_t, std::ranges::iterator_t, std::ranges::equal_to>); + static_assert(!CanInstantiate); + +} // namespace test5 + +// Not indirectly comparable. +namespace test6 { + + struct Empty{}; + struct IntForwardView : std::ranges::view_base { + constexpr forward_iterator begin() const { return {}; } + constexpr forward_iterator end() const { return {}; } + }; + + using View = ForwardView; + using Pattern = IntForwardView; + static_assert( std::ranges::input_range); + static_assert( std::ranges::forward_range); + static_assert( std::ranges::view); + static_assert( std::ranges::view); + static_assert(!std::indirectly_comparable< + std::ranges::iterator_t, std::ranges::iterator_t, std::ranges::equal_to>); + static_assert(!CanInstantiate); + +} // namespace test6 + +// `View` is an input range and `Pattern` is not a tiny range. +namespace test7 { + + using View = InputView; + using Pattern = ForwardView; + static_assert( std::ranges::input_range); + static_assert(!std::ranges::forward_range); + static_assert( std::ranges::forward_range); + LIBCPP_STATIC_ASSERT(!std::ranges::__tiny_range); + static_assert( std::ranges::view); + static_assert( std::ranges::view); + static_assert( std::indirectly_comparable< + std::ranges::iterator_t, std::ranges::iterator_t, std::ranges::equal_to>); + static_assert(!CanInstantiate); + +} // namespace test7 + +// `View` is an input range and `Pattern` is almost a tiny range, except the `size()` function is not `constexpr`. +namespace test8 { + + struct AlmostTinyRange : std::ranges::view_base { + int* begin() const; + int* end() const; + static size_t size() { return 1; } + }; + + using View = InputView; + using Pattern = AlmostTinyRange; + static_assert( std::ranges::input_range); + static_assert(!std::ranges::forward_range); + static_assert( std::ranges::forward_range); + LIBCPP_STATIC_ASSERT(!std::ranges::__tiny_range); + static_assert( std::ranges::view); + static_assert( std::ranges::view); + static_assert( std::indirectly_comparable< + std::ranges::iterator_t, std::ranges::iterator_t, std::ranges::equal_to>); + static_assert(!CanInstantiate); + +} // namespace test8 + +// `View` is an input range and `Pattern` is almost a tiny range, except the `size()` returns a number `>2`. +namespace test9 { + + struct AlmostTinyRange : std::ranges::view_base { + int* begin() const; + int* end() const; + constexpr static size_t size() { return 2; } + }; + + using View = InputView; + using Pattern = ForwardView; + static_assert( std::ranges::input_range); + static_assert(!std::ranges::forward_range); + static_assert( std::ranges::forward_range); + LIBCPP_STATIC_ASSERT(!std::ranges::__tiny_range); + static_assert( std::ranges::view); + static_assert( std::ranges::view); + static_assert( std::indirectly_comparable< + std::ranges::iterator_t, std::ranges::iterator_t, std::ranges::equal_to>); + static_assert(!CanInstantiate); + +} // namespace test9 diff --git a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/ctad.compile.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/ctad.compile.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/ctad.compile.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 +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: libcpp-has-no-incomplete-ranges + +// template +// lazy_split_view(R&&, P&&) -> lazy_split_view, views::all_t

>; +// +// template +// lazy_split_view(R&&, range_value_t) -> lazy_split_view, single_view>>; + +#include + +#include +#include +#include +#include "types.h" + +struct ForwardRange { + forward_iterator begin() const; + forward_iterator end() const; +}; +static_assert( std::ranges::forward_range); + +struct InputRange { + cpp20_input_iterator begin() const; + sentinel_wrapper> end() const; +}; +static_assert(std::ranges::input_range); + +template +constexpr void test() { + I1 i1{}; + I2 i2{}; + + std::ranges::lazy_split_view v(std::move(i1), std::move(i2)); + static_assert(std::same_as>); + using O = decltype(std::move(v).base()); + static_assert(std::same_as); +} + +constexpr void testCtad() { + // (Range, Pattern) + test(); + test, std::ranges::views::all_t>(); + + // (Range, RangeElement) + test, std::ranges::single_view>(); + test, std::ranges::single_view>(); + + // (Range, RangeElement) with implicit conversion. + test, std::ranges::single_view>(); + test, std::ranges::single_view>(); + + // Note: CTAD from (InputRange, ForwardTinyRange) doesn't work -- the deduction guide wraps the pattern in + // `views::all_t`, resulting in `views::owning_view`. That type would never satisfy `tiny-range` + // because `views::owning_view` contains a member function `size()` that shadows the static `size()` in + // `ForwardTinyRange`. +} diff --git a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/ctor.copy_move.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/ctor.copy_move.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/ctor.copy_move.pass.cpp @@ -0,0 +1,90 @@ +//===----------------------------------------------------------------------===// +// +// 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-has-no-incomplete-ranges + +// Test the implicitly-generated copy and move constructors since `lazy_split_view` has non-trivial members. + +#include + +#include +#include +#include +#include "small_string.h" +#include "types.h" + +constexpr bool operator==(const InputView& lhs, const InputView& rhs) { + return SmallString(lhs) == SmallString(rhs); +} + +constexpr bool test() { + // Can copy `lazy_split_view`. + { + // Forward range. + { + std::ranges::lazy_split_view v1("abc def", " "); + auto v2 = v1; + assert(v2.base() == v1.base()); + } + + // Input range. + { + SplitViewInput v1("abc def", ' '); + auto v2 = v1; + assert(v2.base() == v1.base()); + } + } + + // Can move `lazy_split_view`. + { + // Forward range. + { + std::string_view base = "abc def"; + std::ranges::lazy_split_view v1(base, " "); + auto v2 = std::move(v1); + assert(v2.base() == base); + } + + // Input range. + { + InputView base("abc def"); + SplitViewInput v1(base, ' '); + auto v2 = std::move(v1); + assert(v2.base() == base); + } + } + + // `non-propagating-cache` is not copied. + { + SplitViewInput v1("abc def ghi", ' '); + auto outer_iter1 = v1.begin(); + ++outer_iter1; + auto val1 = *outer_iter1; + auto i1 = val1.begin(); + assert(*i1 == 'd'); + ++i1; + assert(*i1 == 'e'); + + auto v2 = v1; + auto val2 = *v2.begin(); + auto i2 = val2.begin(); + assert(*i2 == 'a'); + ++i2; + assert(*i2 == 'b'); + } + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/ctor.default.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/ctor.default.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/ctor.default.pass.cpp @@ -0,0 +1,59 @@ +//===----------------------------------------------------------------------===// +// +// 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-has-no-incomplete-ranges + +// lazy_split_view() requires default_initializable && default_initializable

= default; + +#include + +#include +#include "types.h" + +struct ThrowingDefaultCtorForwardView : std::ranges::view_base { + ThrowingDefaultCtorForwardView() noexcept(false); + forward_iterator begin() const; + forward_iterator end() const; +}; + +struct NoDefaultCtorForwardView : std::ranges::view_base { + NoDefaultCtorForwardView() = delete; + forward_iterator begin() const; + forward_iterator end() const; +}; + +static_assert( std::is_default_constructible_v>); +static_assert(!std::is_default_constructible_v>); +static_assert(!std::is_default_constructible_v>); + +static_assert( std::is_nothrow_default_constructible_v>); +static_assert(!std::is_nothrow_default_constructible_v); +static_assert(!std::is_nothrow_default_constructible_v< + std::ranges::lazy_split_view>); + +constexpr bool test() { + { + std::ranges::lazy_split_view v; + assert(v.base() == CopyableView()); + } + + { + std::ranges::lazy_split_view v = {}; + assert(v.base() == CopyableView()); + } + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/ctor.range.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/ctor.range.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/ctor.range.pass.cpp @@ -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 +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: libcpp-has-no-incomplete-ranges + +// template +// requires constructible_from> && +// constructible_from>> +// constexpr lazy_split_view(Range&& r, range_value_t e); + +#include + +#include +#include +#include +#include +#include "types.h" + +struct ElementWithCounting { + int* times_copied = nullptr; + int* times_moved = nullptr; + + constexpr ElementWithCounting(int& copies_ctr, int& moves_ctr) : times_copied(&copies_ctr), times_moved(&moves_ctr) {} + + constexpr ElementWithCounting(const ElementWithCounting& rhs) + : times_copied(rhs.times_copied) + , times_moved(rhs.times_moved) { + ++(*times_copied); + } + constexpr ElementWithCounting(ElementWithCounting&& rhs) + : times_copied(rhs.times_copied) + , times_moved(rhs.times_moved) { + ++(*times_moved); + } + + constexpr bool operator==(const ElementWithCounting&) const { return true; } +}; + +struct RangeWithCounting { + using value_type = ElementWithCounting; + + int* times_copied = nullptr; + int* times_moved = nullptr; + + constexpr RangeWithCounting(int& copies_ctr, int& moves_ctr) : times_copied(&copies_ctr), times_moved(&moves_ctr) {} + + constexpr RangeWithCounting(const RangeWithCounting& rhs) + : times_copied(rhs.times_copied) + , times_moved(rhs.times_moved) { + ++(*times_copied); + } + constexpr RangeWithCounting(RangeWithCounting&& rhs) + : times_copied(rhs.times_copied) + , times_moved(rhs.times_moved) { + ++(*times_moved); + } + + constexpr const ElementWithCounting* begin() const { return nullptr; } + constexpr const ElementWithCounting* end() const { return nullptr; } + + constexpr RangeWithCounting& operator=(const RangeWithCounting&) = default; + constexpr RangeWithCounting& operator=(RangeWithCounting&&) = default; + constexpr bool operator==(const RangeWithCounting&) const { return true; } +}; +static_assert( std::ranges::forward_range); +static_assert(!std::ranges::view); + +constexpr bool test() { + { + using V = std::ranges::lazy_split_view; + + // Calling the constructor with `(ForwardView, range_value_t)`. + { + const char str[] = "abc def"; + V v(str, " "); + assert(v.base() == std::string_view(str, sizeof(str))); + } + + // Calling the constructor with `(ForwardRange, range_value_t)`. + { + std::string_view str = "abc def"; + V v(str, " "); + assert(v.base() == str); + } + + struct Empty {}; + static_assert(!std::is_constructible_v); + static_assert(!std::is_constructible_v); + } + + // Make sure the arguments are moved, not copied. + { + using Range = RangeWithCounting; + using Element = ElementWithCounting; + // TODO(varconst): use `views::single` once it's implemented. + using Pattern = std::ranges::single_view; + + // Arguments are lvalues. + { + using View = std::ranges::ref_view; + + int range_copied = 0, range_moved = 0, element_copied = 0, element_moved = 0; + Range range(range_copied, range_moved); + Element element(element_copied, element_moved); + + std::ranges::lazy_split_view v(range, element); + assert(range_copied == 0); // `ref_view` does neither copy... + assert(range_moved == 0); // ...nor move the element. + assert(element_copied == 1); // The element is copied into the argument... + assert(element_moved == 1); // ...and moved into the member variable. + } + + // Arguments are rvalues. + { + using View = std::ranges::owning_view; + + int range_copied = 0, range_moved = 0, element_copied = 0, element_moved = 0; + std::ranges::lazy_split_view v( + Range(range_copied, range_moved), Element(element_copied, element_moved)); + assert(range_copied == 0); + assert(range_moved == 1); // `owning_view` moves the given argument. + assert(element_copied == 0); + assert(element_moved == 1); + } + } + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/ctor.view.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/ctor.view.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/ctor.view.pass.cpp @@ -0,0 +1,102 @@ +//===----------------------------------------------------------------------===// +// +// 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-has-no-incomplete-ranges + +// constexpr lazy_split_view(View base, Pattern pattern); + +#include + +#include +#include +#include +#include "types.h" + +struct ViewWithCounting : std::ranges::view_base { + int* times_copied = nullptr; + int* times_moved = nullptr; + + constexpr ViewWithCounting(int& copies_ctr, int& moves_ctr) : times_copied(&copies_ctr), times_moved(&moves_ctr) {} + + constexpr ViewWithCounting(const ViewWithCounting& rhs) + : times_copied(rhs.times_copied) + , times_moved(rhs.times_moved) { + ++(*times_copied); + } + constexpr ViewWithCounting(ViewWithCounting&& rhs) + : times_copied(rhs.times_copied) + , times_moved(rhs.times_moved) { + ++(*times_moved); + } + + constexpr const char* begin() const { return nullptr; } + constexpr const char* end() const { return nullptr; } + + constexpr ViewWithCounting& operator=(const ViewWithCounting&) = default; + constexpr ViewWithCounting& operator=(ViewWithCounting&&) = default; + constexpr bool operator==(const ViewWithCounting&) const { return true; } +}; +static_assert(std::ranges::forward_range); +static_assert(std::ranges::view); + +constexpr bool test() { + // Calling the constructor with `(ForwardView, ForwardView)`. + { + CopyableView input = "abc def"; + std::ranges::lazy_split_view v(input, " "); + assert(v.base() == input); + } + + // Calling the constructor with `(InputView, TinyView)`. + { + InputView input = "abc def"; + std::ranges::lazy_split_view v(input, ' '); + // Note: `InputView` isn't equality comparable. + (void)v; + } + + // Make sure the arguments are moved, not copied. + { + using View = ViewWithCounting; + using Pattern = ViewWithCounting; + + // Arguments are lvalues. + { + int view_copied = 0, view_moved = 0, pattern_copied = 0, pattern_moved = 0; + View view(view_copied, view_moved); + Pattern pattern(pattern_copied, pattern_moved); + + std::ranges::lazy_split_view v(view, pattern); + assert(view_copied == 1); // The local variable is copied into the argument. + assert(view_moved == 1); + assert(pattern_copied == 1); + assert(pattern_moved == 1); + } + + // Arguments are rvalues. + { + int view_copied = 0, view_moved = 0, pattern_copied = 0, pattern_moved = 0; + std::ranges::lazy_split_view v( + View(view_copied, view_moved), Pattern(pattern_copied, pattern_moved)); + assert(view_copied == 0); + assert(view_moved == 1); + assert(pattern_copied == 0); + assert(pattern_moved == 1); + } + } + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/end.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/end.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/end.pass.cpp @@ -0,0 +1,167 @@ +//===----------------------------------------------------------------------===// +// +// 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-has-no-incomplete-ranges + +// constexpr auto end() requires forward_range && common_range; +// constexpr auto end() const; + +#include + +#include +#include +#include "test_iterators.h" +#include "types.h" + +struct ForwardViewCommonIfConst : std::ranges::view_base { + std::string_view view_; + constexpr explicit ForwardViewCommonIfConst() = default; + constexpr ForwardViewCommonIfConst(const char* ptr) : view_(ptr) {} + constexpr ForwardViewCommonIfConst(std::string_view v) : view_(v) {} + constexpr ForwardViewCommonIfConst(ForwardViewCommonIfConst&&) = default; + constexpr ForwardViewCommonIfConst& operator=(ForwardViewCommonIfConst&&) = default; + constexpr ForwardViewCommonIfConst(const ForwardViewCommonIfConst&) = default; + constexpr ForwardViewCommonIfConst& operator=(const ForwardViewCommonIfConst&) = default; + constexpr forward_iterator begin() { return forward_iterator(nullptr); } + constexpr std::default_sentinel_t end() { return std::default_sentinel; } + constexpr forward_iterator begin() const { return forward_iterator(view_.begin()); } + constexpr forward_iterator end() const { return forward_iterator(view_.end()); } +}; +bool operator==(forward_iterator, std::default_sentinel_t) { return false; } + +struct ForwardViewNonCommonRange : std::ranges::view_base { + std::string_view view_; + constexpr explicit ForwardViewNonCommonRange() = default; + constexpr ForwardViewNonCommonRange(const char* ptr) : view_(ptr) {} + constexpr ForwardViewNonCommonRange(std::string_view v) : view_(v) {} + constexpr ForwardViewNonCommonRange(ForwardViewNonCommonRange&&) = default; + constexpr ForwardViewNonCommonRange& operator=(ForwardViewNonCommonRange&&) = default; + constexpr ForwardViewNonCommonRange(const ForwardViewNonCommonRange&) = default; + constexpr ForwardViewNonCommonRange& operator=(const ForwardViewNonCommonRange&) = default; + constexpr forward_iterator begin() { return forward_iterator(nullptr); } + constexpr std::default_sentinel_t end() { return std::default_sentinel; } + constexpr forward_iterator begin() const { return forward_iterator(view_.begin()); } + constexpr std::default_sentinel_t end() const { return std::default_sentinel; } +}; +bool operator==(forward_iterator, std::default_sentinel_t) { return false; } + +constexpr bool test() { + // non-const: forward_range && simple_view && simple_view

-> outer-iterator + // const: forward_range && common_range -> outer-iterator + { + using V = ForwardView; + using P = V; + + static_assert(std::ranges::forward_range); + static_assert(std::ranges::common_range); + LIBCPP_STATIC_ASSERT(std::ranges::__simple_view); + LIBCPP_STATIC_ASSERT(std::ranges::__simple_view

); + + { + std::ranges::lazy_split_view v; + auto it = v.end(); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + } + + { + const std::ranges::lazy_split_view cv; + auto it = cv.end(); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + } + } + + // non-const: forward_range && common_range && simple_view && !simple_view

-> outer-iterator + // const: forward_range && forward_range && common_range -> outer-iterator + { + using V = ForwardView; + using P = ForwardDiffView; + + static_assert(std::ranges::forward_range); + static_assert(std::ranges::common_range); + LIBCPP_STATIC_ASSERT(std::ranges::__simple_view); + LIBCPP_STATIC_ASSERT(!std::ranges::__simple_view

); + static_assert(std::ranges::forward_range); + static_assert(std::ranges::common_range); + + { + std::ranges::lazy_split_view v; + auto it = v.end(); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + } + + { + const std::ranges::lazy_split_view cv; + auto it = cv.end(); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + } + } + + // non-const: forward_range && !common_range -> disabled + // const: forward_range && forward_range && common_range -> outer-iterator + { + using V = ForwardViewCommonIfConst; + using P = V; + + static_assert(std::ranges::forward_range); + static_assert(!std::ranges::common_range); + static_assert(std::ranges::forward_range); + static_assert(std::ranges::common_range); + + { + std::ranges::lazy_split_view v; + auto it = v.begin(); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + } + + { + const std::ranges::lazy_split_view cv; + auto it = cv.begin(); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + } + } + + // non-const: forward_range && !common_range -> disabled + // const: forward_range && forward_range && !common_range -> outer-iterator + { + using V = ForwardViewNonCommonRange; + using P = V; + + static_assert(std::ranges::forward_range); + static_assert(!std::ranges::common_range); + static_assert(std::ranges::forward_range); + static_assert(!std::ranges::common_range); + + { + std::ranges::lazy_split_view v; + auto it = v.end(); + static_assert(std::same_as); + } + + { + const std::ranges::lazy_split_view cv; + auto it = cv.end(); + static_assert(std::same_as); + } + } + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/general.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/general.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/general.pass.cpp @@ -0,0 +1,401 @@ +//===----------------------------------------------------------------------===// +// +// 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-has-no-incomplete-ranges + +// template +// class std::ranges::lazy_split_view; +// +// These test check the output `lazy_split_view` produces for a variety of inputs, including many corner cases, with no +// restrictions on which member functions can be called. + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include "small_string.h" +#include "types.h" + +template +constexpr bool is_equal(View& view, const Expected& expected) { + using Char = std::ranges::range_value_t>; + using Str = BasicSmallString; + + auto actual_it = view.begin(); + auto expected_it = expected.begin(); + for (; actual_it != view.end() && expected_it != expected.end(); ++actual_it, ++expected_it) { + if (Str(*actual_it) != Str(*expected_it)) + return false; + } + + return actual_it == view.end() && expected_it == expected.end(); +} + +template +constexpr bool test_function_call(T&& input, Separator&& separator, std::array expected) { + std::ranges::lazy_split_view v(input, separator); + return is_equal(v, expected); +} + +template +constexpr bool test_with_piping(T&& input, Separator&& separator, std::array expected) { + auto expected_it = expected.begin(); + for (auto e : input | std::ranges::views::lazy_split(separator)) { + if (expected_it == expected.end()) + return false; + if (SmallString(e) != *expected_it) + return false; + + ++expected_it; + } + + return expected_it == expected.end(); +} + +constexpr bool test_l_r_values() { + using namespace std::string_view_literals; + + // Both lvalues and rvalues can be used as input. + { + // Lvalues. + { + auto input = "abc"sv; + auto sep = " "sv; + [[maybe_unused]] std::ranges::lazy_split_view v(input, sep); + } + + // Const lvalues. + { + const auto input = "abc"sv; + const auto sep = " "sv; + [[maybe_unused]] std::ranges::lazy_split_view v(input, sep); + } + + // Rvalues. + { + auto input = "abc"sv; + auto sep = " "sv; + [[maybe_unused]] std::ranges::lazy_split_view v(std::move(input), std::move(sep)); + } + + // Const rvalues. + { + const auto input = "abc"sv; + const auto sep = " "sv; + [[maybe_unused]] std::ranges::lazy_split_view v(std::move(input), std::move(sep)); + } + } + + return true; +} + +constexpr bool test_string_literal_separator() { + using namespace std::string_view_literals; + + // Splitting works as expected when the separator is a single character literal. + { + std::ranges::lazy_split_view v("abc def"sv, ' '); + assert(is_equal(v, std::array{"abc"sv, "def"sv})); + } + + // Counterintuitively, a seemingly equivalent separator expressed as a string literal doesn't match anything. This is + // because of the implicit terminating null in the literal. + { + std::ranges::lazy_split_view v("abc def"sv, " "); + assert(is_equal(v, std::array{"abc def"sv})); + } + + // To illustrate the previous point further, the separator is actually a two-character string literal: `{' ', '\0'}`. + // Should the input string contain that two-character sequence, the separator would match. + { + std::ranges::lazy_split_view v("abc \0def"sv, " "); + assert(is_equal(v, std::array{"abc"sv, "def"sv})); + } + + return true; +} + +// Make sure that a string literal and a `string_view` produce the same results (which isn't always the case, see +// below). +template +constexpr std::string_view sv(T&& str) { + return std::string_view(str); +}; + +template +constexpr void test_one(T&& input, Separator&& separator, std::array expected) { + assert(test_function_call(input, separator, expected)); + assert(test_with_piping(input, separator, expected)); + + // In addition to the `(ForwardView, ForwardView)` case, test the `(ForwardView, tiny-range)` and `(InputView, + // tiny-range)` cases (all of which have unique code paths). + if constexpr (std::is_same_v, char>) { + assert(test_function_call(CopyableView(input), ForwardTinyView(separator), expected)); + assert(test_with_piping(CopyableView(input), ForwardTinyView(separator), expected)); + + assert(test_function_call(InputView(input), ForwardTinyView(separator), expected)); + assert(test_with_piping(InputView(input), ForwardTinyView(separator), expected)); + } +} + +constexpr bool test_string_literals() { + // These tests show characteristic examples of how using string literals with `lazy_split_view` produces unexpected + // results due to the implicit terminating null that is treated as part of the range. + + using namespace std::string_view_literals; + + char short_sep = ' '; + auto long_sep = "12"sv; + + // When splitting a string literal, only the last segment will be null-terminated (getting the terminating null from + // the original range). + { + std::array expected = {"abc"sv, std::string_view("def", sizeof("def"))}; + + assert(test_function_call("abc def", short_sep, expected)); + assert(test_with_piping("abc def", short_sep, expected)); + assert(test_function_call("abc12def", long_sep, expected)); + assert(test_with_piping("abc12def", long_sep, expected)); + } + + // Empty string. + { + // Because an empty string literal contains an implicit terminating null, the output will contain one segment. + std::array expected = {std::string_view("", 1)}; + + assert(test_function_call("", short_sep, expected)); + assert(test_with_piping("", short_sep, expected)); + assert(test_function_call("", long_sep, expected)); + assert(test_with_piping("", long_sep, expected)); + } + + // Terminating null in the separator -- the character literal `' '` and the seemingly equivalent string literal `" "` + // are treated differently due to the presence of an implicit `\0` in the latter. + { + const char input[] = "abc def"; + std::array expected_unsplit = {std::string_view(input, sizeof(input))}; + std::array expected_split = {"abc"sv, std::string_view("def", sizeof("def"))}; + + assert(test_function_call(input, " ", expected_unsplit)); + assert(test_function_call("abc \0def", " ", expected_split)); + // Note: string literals don't work with piping because arrays decay to pointers, and pointers don't model `range`. + } + + // Empty separator. + { + auto empty_sep = ""sv; + std::array expected = {"a"sv, "b"sv, "c"sv, "\0"sv}; + + assert(test_function_call("abc", empty_sep, expected)); + assert(test_with_piping("abc", empty_sep, expected)); + } + + return true; +} + +bool test_nontrivial_characters() { + // Try a deliberately heavyweight "character" type to see if it triggers any corner cases. + + using Map = std::map; + using Vec = std::vector; + + Map sep = {{"yyy", 999}}; + Map m1 = { + {"a", 1}, + {"bc", 2}, + }; + Map m2 = { + {"def", 3}, + }; + Map m3 = { + {"g", 4}, + {"hijk", 5}, + }; + + Vec expected1 = {m1, m2}; + Vec expected2 = {m3}; + + std::ranges::lazy_split_view v(Vec{m1, m2, sep, m3}, sep); + + // Segment 1: {m1, m2} + auto outer = v.begin(); + assert(outer != v.end()); + auto inner = (*outer).begin(); + assert(*inner++ == m1); + assert(*inner++ == m2); + assert(inner == (*outer).end()); + + // Segment 2: {m3} + ++outer; + assert(outer != v.end()); + inner = (*outer).begin(); + assert(*inner++ == m3); + assert(inner == (*outer).end()); + + ++outer; + assert(outer == v.end()); + + return true; +} + +constexpr bool main_test() { + using namespace std::string_view_literals; + + char short_sep = ' '; + auto long_sep = "12"sv; + + // One separator. + { + std::array expected = {"abc"sv, "def"sv}; + test_one("abc def"sv, short_sep, expected); + test_one("abc12def"sv, long_sep, expected); + } + + // Several separators in a row. + { + std::array expected = {"abc"sv, ""sv, ""sv, ""sv, "def"sv}; + test_one("abc def"sv, short_sep, expected); + test_one("abc12121212def"sv, long_sep, expected); + } + + // Trailing separator. + { + std::array expected = {"abc"sv, "def"sv, ""sv}; + test_one("abc def "sv, short_sep, expected); + test_one("abc12def12"sv, long_sep, expected); + } + + // Leading separator. + { + std::array expected = {""sv, "abc"sv, "def"sv}; + test_one(" abc def"sv, short_sep, expected); + test_one("12abc12def"sv, long_sep, expected); + } + + // No separator. + { + std::array expected = {"abc"sv}; + test_one("abc"sv, short_sep, expected); + test_one("abc"sv, long_sep, expected); + } + + // Input consisting of a single separator. + { + std::array expected = {""sv, ""sv}; + test_one(" "sv, short_sep, expected); + test_one("12"sv, long_sep, expected); + } + + // Input consisting of only separators. + { + std::array expected = {""sv, ""sv, ""sv, ""sv}; + test_one(" "sv, short_sep, expected); + test_one("121212"sv, long_sep, expected); + } + + // The separator and the string use the same character only. + { + auto overlapping_sep = "aaa"sv; + std::array expected = {""sv, "aa"sv}; + test_one("aaaaa"sv, overlapping_sep, expected); + } + + // Many redundant separators. + { + std::array expected = {""sv, ""sv, "abc"sv, ""sv, ""sv, "def"sv, ""sv, ""sv}; + test_one(" abc def "sv, short_sep, expected); + test_one("1212abc121212def1212"sv, long_sep, expected); + } + + // Separators after every character. + { + std::array expected = {""sv, "a"sv, "b"sv, "c"sv, ""sv}; + test_one(" a b c "sv, short_sep, expected); + test_one("12a12b12c12"sv, long_sep, expected); + } + + // Overlap between the separator and the string (see https://wg21.link/lwg3505). + { + auto overlapping_sep = "ab"sv; + std::array expected = {"a"sv, "aa"sv, ""sv, "b"sv}; + test_one("aabaaababb"sv, overlapping_sep, expected); + } + + // Empty input. + { + std::array expected = {}; + test_one(""sv, short_sep, expected); + test_one(""sv, long_sep, expected); + } + + // Empty separator. + { + auto empty_sep = ""sv; + std::array expected = {"a"sv, "b"sv, "c"sv}; + test_one("abc"sv, empty_sep, expected); + test_one("abc"sv, empty_sep, expected); + } + + // Terminating null as a separator. + { + std::array expected = {"abc"sv, "def"sv}; + test_one("abc\0def"sv, '\0', expected); + test_one("abc\0\0def"sv, "\0\0"sv, expected); + } + + // Different character types. + { + // `char`. + test_function_call("abc def", ' ', std::array{"abc", "def"}); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + // `wchar_t`. + test_function_call(L"abc def", L' ', std::array{L"abc", L"def"}); +#endif + // `char8_t`. + test_function_call(u8"abc def", u8' ', std::array{u8"abc", u8"def"}); + // `char16_t`. + test_function_call(u"abc def", u' ', std::array{u"abc", u"def"}); + // `char32_t`. + test_function_call(U"abc def", U' ', std::array{U"abc", U"def"}); + } + + // Non-character input. + { + std::array expected = {std::array{1, 2, 3}, std::array{4, 5, 6}}; + test_one(std::array{1, 2, 3, 0, 4, 5, 6}, 0, expected); + test_one(std::array{1, 2, 3, 0, 0, 0, 4, 5, 6}, std::array{0, 0, 0}, expected); + } + + return true; +} + +int main(int, char**) { + main_test(); + static_assert(main_test()); + + test_string_literals(); + static_assert(test_string_literals()); + + test_l_r_values(); + static_assert(test_l_r_values()); + + test_string_literal_separator(); + static_assert(test_string_literal_separator()); + + // Note: map is not `constexpr`, so this test is runtime-only. + test_nontrivial_characters(); + + return 0; +} diff --git a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.inner/base.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.inner/base.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.inner/base.pass.cpp @@ -0,0 +1,93 @@ +//===----------------------------------------------------------------------===// +// +// 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-has-no-incomplete-ranges + +// constexpr const iterator_t& inner-iterator::base() const& noexcept; +// +// constexpr iterator_t inner-iterator::base() && +// requires forward_range; + +#include + +#include +#include +#include "../types.h" + +static_assert( noexcept(std::declval().base())); +static_assert( noexcept(std::declval().base())); +static_assert( noexcept(std::declval().base())); +static_assert( noexcept(std::declval().base())); +static_assert( noexcept(std::declval().base())); +static_assert( noexcept(std::declval().base())); + +constexpr bool test() { + // `base` works with a forward view (two different overloads based on ref-qualification of the `inner-iterator`). + { + using BaseIter = std::ranges::iterator_t; + CopyableView b("abc def"); + std::ranges::lazy_split_view v(b, " "); + auto i = (*v.begin()).begin(); + const auto ci = i; + + { + std::same_as decltype(auto) base = i.base(); + assert(base == b.begin()); + } + + { + std::same_as decltype(auto) base = ci.base(); + assert(base == b.begin()); + } + + { + std::same_as decltype(auto) base = std::move(i).base(); + assert(base == b.begin()); + } + + { + std::same_as decltype(auto) base = std::move(ci).base(); + assert(base == b.begin()); + } + } + + // `base` works with an input view (no overloads). + { + using BaseIter = std::ranges::iterator_t; + InputView b("abc def"); + std::ranges::lazy_split_view v(b, ' '); + auto i = (*v.begin()).begin(); + const auto ci = i; + + { + [[maybe_unused]] std::same_as decltype(auto) base = i.base(); + } + + { + [[maybe_unused]] std::same_as decltype(auto) base = ci.base(); + } + + { + [[maybe_unused]] std::same_as decltype(auto) base = std::move(i).base(); + } + + { + [[maybe_unused]] std::same_as decltype(auto) base = std::move(ci).base(); + } + } + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.inner/ctor.default.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.inner/ctor.default.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.inner/ctor.default.pass.cpp @@ -0,0 +1,35 @@ +//===----------------------------------------------------------------------===// +// +// 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-has-no-incomplete-ranges + +// constexpr inner-iterator::inner-iterator() = default; + +#include + +#include "../types.h" + +constexpr bool test() { + { + [[maybe_unused]] InnerIterForward i; + } + + { + [[maybe_unused]] InnerIterInput i; + } + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.inner/ctor.outer_iterator.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.inner/ctor.outer_iterator.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.inner/ctor.outer_iterator.pass.cpp @@ -0,0 +1,44 @@ +//===----------------------------------------------------------------------===// +// +// 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-has-no-incomplete-ranges + +// constexpr explicit inner-iterator::inner-iterator(outer-iterator i); + +#include + +#include "../types.h" + +static_assert(!std::is_constructible_v); + +template +constexpr void test_impl() { + [[maybe_unused]] Inner i(Outer{}); + // Verify that the constructor is `explicit`. + static_assert(!std::is_convertible_v); +} + +constexpr bool test() { + test_impl(); + test_impl(); +// Is only constructible if both the outer and the inner iterators have the same constness. + test_impl(); +// Note: this works because of an implicit conversion (`OuterIterNonConst` is converted to `OuterIterConst`). + test_impl(); + test_impl(); + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.inner/deref.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.inner/deref.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.inner/deref.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 +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: libcpp-has-no-incomplete-ranges + +// constexpr decltype(auto) inner-iterator::operator*() const; + +#include + +#include "../types.h" + +constexpr bool test() { + // Can call `inner-iterator::operator*`; `View` is a forward range. + { + SplitViewDiff v("abc def", " "); + auto val = *v.begin(); + + // Non-const iterator. + { + auto i = val.begin(); + static_assert(std::same_as); + assert(*i == 'a'); + assert(*(++i) == 'b'); + assert(*(++i) == 'c'); + } + + // Const iterator. + { + const auto ci = val.begin(); + static_assert(std::same_as); + assert(*ci == 'a'); + } + } + + // Can call `inner-iterator::operator*`; `View` is an input range. + { + SplitViewInput v("abc def", ' '); + auto val = *v.begin(); + + // Non-const iterator. + { + auto i = val.begin(); + static_assert(std::same_as); + assert(*i == 'a'); + assert(*(++i) == 'b'); + assert(*(++i) == 'c'); + } + + // Const iterator. + { + const auto ci = val.begin(); + static_assert(std::same_as); + // Note: when the underlying range is an input range, `current` is stored in the `lazy_split_view` itself and + // shared between `inner-iterator`s. Consequently, incrementing one iterator effectively increments all of them. + assert(*ci == 'c'); + } + } + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.inner/equal.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.inner/equal.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.inner/equal.pass.cpp @@ -0,0 +1,76 @@ +//===----------------------------------------------------------------------===// +// +// 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-has-no-incomplete-ranges + +// friend constexpr bool operator==(const inner-iterator& x, const inner-iterator& y); +// requires forward_range; +// +// friend constexpr bool operator==(const inner-iterator& x, default_sentinel_t); + +#include + +#include +#include +#include "../types.h" + +template +concept CanCallEquals = requires(const Iter& i) { + i == i; + i != i; +}; + +constexpr bool test() { + // When `View` is a forward range, `inner-iterator` supports both overloads of `operator==`. + { + SplitViewForward v("abc def", " "); + auto val = *v.begin(); + auto b = val.begin(); + std::same_as decltype(auto) e = val.end(); + + // inner-iterator == inner-iterator + { + assert(b == b); + assert(!(b != b)); + } + + // inner-iterator == default_sentinel + { + assert(!(b == e)); + assert(b != e); + + assert(!(b == std::default_sentinel)); + assert(b != std::default_sentinel); + } + } + + // When `View` is an input range, `inner-iterator only supports comparing an `inner-iterator` to the default sentinel. + { + SplitViewInput v("abc def", ' '); + auto val = *v.begin(); + auto b = val.begin(); + std::same_as decltype(auto) e = val.end(); + + static_assert(!CanCallEquals); + + assert(!(b == std::default_sentinel)); + assert(b != std::default_sentinel); + assert(!(b == e)); + assert(b != e); + } + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.inner/increment.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.inner/increment.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.inner/increment.pass.cpp @@ -0,0 +1,133 @@ +//===----------------------------------------------------------------------===// +// +// 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-has-no-incomplete-ranges + +// constexpr inner-iterator& inner-iterator::operator++(); +// +// constexpr decltype(auto) inner-iterator::operator++(int); + +#include + +#include +#include +#include "test_macros.h" +#include "../types.h" + +struct EmptyView : std::ranges::view_base { + constexpr int* begin() const { return nullptr; } + constexpr int* end() const { return nullptr; } + constexpr static size_t size() { return 0; } +}; +static_assert(std::ranges::forward_range); +static_assert(std::ranges::view); +LIBCPP_STATIC_ASSERT(std::ranges::__tiny_range); + +constexpr bool test() { + // Can call `inner-iterator::operator++`; `View` is a forward range. + { + SplitViewForward v("abc def", " "); + auto val = *v.begin(); + + // ++i + { + auto i = val.begin(); + assert(*i == 'a'); + + decltype(auto) i2 = ++i; + static_assert(std::is_lvalue_reference_v); + assert(&i2 == &i); + assert(*i2 == 'b'); + } + + // i++ + { + auto i = val.begin(); + assert(*i == 'a'); + + decltype(auto) i2 = i++; + static_assert(!std::is_reference_v); + assert(*i2 == 'a'); + assert(*i == 'b'); + } + } + + // Can call `inner-iterator::operator++`; `View` is an input range. + { + // ++i + { + SplitViewInput v("abc def", ' '); + auto val = *v.begin(); + + auto i = val.begin(); + assert(*i == 'a'); + + decltype(auto) i2 = ++i; + static_assert(std::is_lvalue_reference_v); + assert(&i2 == &i); + assert(*i2 == 'b'); + } + + // i++ + { + SplitViewInput v("abc def", ' '); + auto val = *v.begin(); + + auto i = val.begin(); + assert(*i == 'a'); + + static_assert(std::is_void_v); + i++; + assert(*i == 'b'); + } + } + + // Can call `inner-iterator::operator++`; `View` is an input range and `Pattern` is an "empty" range. + { + // ++i + { + std::ranges::lazy_split_view v("a", EmptyView()); + auto val = *v.begin(); + + auto i = val.begin(); + assert(*i.base() == 'a'); + assert(i != std::default_sentinel); + + // The iterator doesn't move to the next character but is considered to point to the end. + decltype(auto) i2 = ++i; + assert(&i2 == &i); + assert(*i2.base() == 'a'); + assert(i2 == std::default_sentinel); + } + + // i++ + { + std::ranges::lazy_split_view v("a", EmptyView()); + auto val = *v.begin(); + + auto i = val.begin(); + assert(*i.base() == 'a'); + assert(i != std::default_sentinel); + + // The iterator doesn't move to the next character but is considered to point to the end. + i++; + assert(*i.base() == 'a'); + assert(i == std::default_sentinel); + } + } + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.inner/iter_move.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.inner/iter_move.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.inner/iter_move.pass.cpp @@ -0,0 +1,158 @@ +//===----------------------------------------------------------------------===// +// +// 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-has-no-incomplete-ranges + +// friend constexpr decltype(auto) iter_move(const inner-iterator& i) +// noexcept(noexcept(ranges::iter_move(i.i_.))); + +#include + +#include +#include +#include +#include "../types.h" + +namespace adl { + +template +struct Iterator { + using value_type = int; + using difference_type = ptrdiff_t; + + value_type* ptr_ = nullptr; + int* iter_move_invocations_ = nullptr; + + constexpr Iterator() = default; + constexpr explicit Iterator(int* p, int& iter_moves) : ptr_(p), iter_move_invocations_(&iter_moves) {} + + constexpr value_type& operator*() const { return *ptr_; } + + Iterator& operator++() { ++ptr_; return *this; } + Iterator operator++(int) { + Iterator prev = *this; + ++ptr_; + return prev; + } + + constexpr Iterator& operator--() { --ptr_; return *this; } + constexpr Iterator operator--(int) { + Iterator prev = *this; + --ptr_; + return prev; + } + + constexpr friend value_type&& iter_move(Iterator iter) noexcept(IsNoexcept) { + if (iter.iter_move_invocations_) { + ++(*iter.iter_move_invocations_); + } + return std::move(*iter); + } + + friend bool operator==(const Iterator& lhs, const Iterator& rhs) { return lhs.ptr_ == rhs.ptr_; } +}; + +template +struct View : std::ranges::view_base { + static constexpr int N = 3; + int a[N] = {0, 1, 2}; + int* iter_moves = nullptr; + + constexpr View() = default; + constexpr View(int& iter_move_invocations) : iter_moves(&iter_move_invocations) { + } + + constexpr adl::Iterator begin() { return adl::Iterator(a, *iter_moves); } + constexpr adl::Iterator end() { return adl::Iterator(a + N, *iter_moves); } +}; + +} // namespace adl + +constexpr bool test() { + // Can use `iter_move` with `inner-iterator`; `View` is a forward range. + { + SplitViewForward v("abc def", " "); + auto segment = *v.begin(); + + // Non-const iterator. + { + auto i = segment.begin(); + static_assert(std::same_as); + assert(iter_move(i) == 'a'); + } + + // Const iterator. + { + const auto i = segment.begin(); + static_assert(std::same_as); + assert(iter_move(i) == 'a'); + } + } + + // Can use `iter_move` with `inner-iterator`, `View` is an input range. + { + SplitViewInput v("abc def", ' '); + auto segment = *v.begin(); + + // Non-const iterator. + { + auto i = segment.begin(); + static_assert(std::same_as); + assert(iter_move(i) == 'a'); + } + + // Const iterator. + { + const auto i = segment.begin(); + static_assert(std::same_as); + assert(iter_move(i) == 'a'); + } + } + + // Ensure the `iter_move` customization point is being used. + { + int iter_move_invocations = 0; + adl::View<> input(iter_move_invocations); + std::ranges::lazy_split_view, adl::View<>> v(input, adl::View<>()); + + auto segment = *v.begin(); + auto i = segment.begin(); + int x = iter_move(i); + assert(x == 0); + assert(iter_move_invocations == 1); + } + + // Check the `noexcept` specification. + { + { + using ThrowingSplitView = std::ranges::lazy_split_view, adl::View>; + using ThrowingValueType = std::ranges::iterator_t::value_type; + using ThrowingIter = std::ranges::iterator_t; + ASSERT_NOT_NOEXCEPT(std::ranges::iter_move(std::declval>())); + ASSERT_NOT_NOEXCEPT(iter_move(std::declval())); + } + + { + using NoexceptSplitView = std::ranges::lazy_split_view, adl::View>; + using NoexceptValueType = std::ranges::iterator_t::value_type; + using NoexceptIter = std::ranges::iterator_t; + ASSERT_NOEXCEPT(std::ranges::iter_move(std::declval>())); + ASSERT_NOEXCEPT(iter_move(std::declval())); + } + } + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.inner/iter_swap.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.inner/iter_swap.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.inner/iter_swap.pass.cpp @@ -0,0 +1,215 @@ +//===----------------------------------------------------------------------===// +// +// 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-has-no-incomplete-ranges + +// friend constexpr void iter_swap(const inner-iterator& x, const inner-iterator& y) +// noexcept(noexcept(ranges::iter_swap(x.i_., y.i_.))) +// requires indirectly_swappable>; + +#include + +#include +#include +#include +#include "../types.h" + +namespace adl { + +template +struct Iterator { + using value_type = int; + using difference_type = ptrdiff_t; + + value_type* ptr_ = nullptr; + int* iter_swap_invocations_ = nullptr; + + constexpr Iterator() = default; + constexpr explicit Iterator(int& iter_swaps) : iter_swap_invocations_(&iter_swaps) {} + + value_type& operator*() const { return *ptr_; } + + Iterator& operator++() { ++ptr_; return *this; } + Iterator operator++(int) { + Iterator prev = *this; + ++ptr_; + return prev; + } + + Iterator& operator--() { --ptr_; return *this; } + Iterator operator--(int) { + Iterator prev = *this; + --ptr_; + return prev; + } + + constexpr friend void iter_swap(Iterator a, Iterator) noexcept(IsNoexcept) { + if (a.iter_swap_invocations_) { + ++(*a.iter_swap_invocations_); + } + } + + friend bool operator==(const Iterator& lhs, const Iterator& rhs) { return lhs.ptr_ == rhs.ptr_; } +}; + +template +struct View : std::ranges::view_base { + int* iter_swaps = nullptr; + + constexpr View() = default; + constexpr View(int& iter_swap_invocations) : iter_swaps(&iter_swap_invocations) { + } + + constexpr adl::Iterator begin() { return adl::Iterator(*iter_swaps); } + constexpr adl::Iterator end() { return adl::Iterator(*iter_swaps); } +}; + +} // namespace adl + +constexpr bool test() { + // Can use `iter_swap` with `inner-iterator`; `View` is a forward range. + { + // Non-const iterator. + { + SplitViewDiff v("abc def", " "); + auto segment = *v.begin(); + + auto i1 = segment.begin(); + auto i2 = i1++; + static_assert(std::is_void_v); + assert(*i1 == 'b'); + assert(*i2 == 'a'); + + iter_swap(i1, i2); + assert(*i1 == 'a'); + assert(*i2 == 'b'); + // Note that `iter_swap` swaps characters in the actual underlying range. + assert(*v.base().begin() == 'b'); + } + + // Const iterator. + { + SplitViewDiff v("abc def", " "); + auto segment = *v.begin(); + + auto i1 = segment.begin(); + const auto i2 = i1++; + static_assert(std::is_void_v); + static_assert(std::is_void_v); + assert(*i1 == 'b'); + assert(*i2 == 'a'); + + iter_swap(i1, i2); + assert(*i1 == 'a'); + assert(*i2 == 'b'); + assert(*v.base().begin() == 'b'); + } + } + + // Can use `iter_swap` with `inner-iterator`; `View` is an input range. + { + + // Non-const iterator. + { + // Iterators belong to the same view. + { + SplitViewInput v("abc def", ' '); + auto segment = *v.begin(); + + auto i1 = segment.begin(); + auto i2 = i1; + ++i1; + static_assert(std::is_void_v); + assert(*i1 == 'b'); + // For an input view, all inner iterators are essentially thin proxies to the same underlying iterator. + assert(*i2 == 'b'); + + iter_swap(i1, i2); + assert(*i1 == 'b'); + assert(*i2 == 'b'); + } + + // Iterators belong to different views. + { + SplitViewInput v1("abc def", ' '); + auto val1 = *v1.begin(); + SplitViewInput v2 = v1; + auto val2 = *v2.begin(); + + auto i1 = val1.begin(); + auto i2 = val2.begin(); + ++i1; + assert(*i1 == 'b'); + assert(*i2 == 'a'); + + iter_swap(i1, i2); + assert(*i1 == 'a'); + assert(*i2 == 'b'); + } + } + + // Const iterator. + { + SplitViewInput v("abc def", ' '); + auto segment = *v.begin(); + + const auto i1 = segment.begin(); + const auto i2 = i1; + static_assert(std::is_void_v); + assert(*i1 == 'a'); + assert(*i2 == 'a'); + + iter_swap(i1, i2); + assert(*i1 == 'a'); + assert(*i2 == 'a'); + } + } + + // Ensure the `iter_swap` customization point is being used. + { + int iter_swap_invocations = 0; + adl::View<> input(iter_swap_invocations); + std::ranges::lazy_split_view, adl::View<>> v(input, adl::View<>()); + + auto segment = *v.begin(); + auto i = segment.begin(); + iter_swap(i, i); + assert(iter_swap_invocations == 1); + } + + // Check the `noexcept` specification. + { + { + using ThrowingSplitView = std::ranges::lazy_split_view, adl::View>; + using ThrowingValueType = std::ranges::iterator_t::value_type; + using ThrowingIter = std::ranges::iterator_t; + ASSERT_NOT_NOEXCEPT( + std::ranges::iter_swap(std::declval>(), std::declval>())); + ASSERT_NOT_NOEXCEPT(iter_swap(std::declval(), std::declval())); + } + + { + using NoexceptSplitView = std::ranges::lazy_split_view, adl::View>; + using NoexceptValueType = std::ranges::iterator_t::value_type; + using NoexceptIter = std::ranges::iterator_t; + ASSERT_NOEXCEPT( + std::ranges::iter_swap(std::declval>(), std::declval>())); + ASSERT_NOEXCEPT(iter_swap(std::declval(), std::declval())); + } + } + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.inner/types.compile.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.inner/types.compile.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.inner/types.compile.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-has-no-incomplete-ranges + +// using iterator_category = If< +// derived_from>::iterator_category, forward_iterator_tag>, +// forward_iterator_tag, +// typename iterator_traits>::iterator_category +// >; +// using iterator_concept = typename outer-iterator::iterator_concept; +// using value_type = range_value_t; +// using difference_type = range_difference_t; + +#include + +#include +#include +#include "../types.h" + +template +using OuterIter = std::ranges::iterator_t>; +template +using InnerIter = std::ranges::iterator_t())>; + +// iterator_concept + +static_assert(std::same_as::iterator_concept, + typename OuterIter::iterator_concept>); +static_assert(std::same_as::iterator_concept, + typename OuterIter::iterator_concept>); + +// iterator_category + +static_assert(std::same_as::iterator_category, std::forward_iterator_tag>); + +template +concept NoIteratorCategory = !requires { typename InnerIter::iterator_category; }; +static_assert(NoIteratorCategory); + +// value_type + +static_assert(std::same_as::value_type, + std::ranges::range_value_t>); + +// difference_type + +static_assert(std::same_as::difference_type, + std::ranges::range_difference_t>); diff --git a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer.value/begin.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer.value/begin.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer.value/begin.pass.cpp @@ -0,0 +1,68 @@ +//===----------------------------------------------------------------------===// +// +// 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-has-no-incomplete-ranges + +// std::ranges::lazy_split_view::outer-iterator::value_type::begin() + +#include + +#include +#include "../types.h" + +constexpr bool test() { + // `View` is a forward range. + { + CopyableView input("a"); + + // Non-const. + { + SplitViewCopyable v(input, "b"); + auto val = *v.begin(); + assert(val.begin().base() == input.begin()); + } + + // Const. + { + SplitViewCopyable v(input, "b"); + const auto val = *v.begin(); + assert(val.begin().base() == input.begin()); + } + } + + // `View` is an input range. + { + InputView input("a"); + + // Non-const. + { + SplitViewInput v(input, 'b'); + auto val = *v.begin(); + // Copies of `InputView` are independent and the iterators won't compare the same. + assert(*val.begin().base() == *input.begin()); + } + + // Const. + { + SplitViewInput v(input, 'b'); + const auto val = *v.begin(); + // Copies of `InputView` are independent and the iterators won't compare the same. + assert(*val.begin().base() == *input.begin()); + } + } + + return true; +} + +int main(int, char**) { + assert(test()); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer.value/ctor.default.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer.value/ctor.default.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer.value/ctor.default.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-has-no-incomplete-ranges + +// std::ranges::lazy_split_view::outer-iterator::value_type::value_type() + +#include + +#include "../types.h" + +constexpr bool test() { + { + [[maybe_unused]] ValueTypeForward val; + } + + { + [[maybe_unused]] ValueTypeForward val = {}; + } + + { + [[maybe_unused]] ValueTypeInput val; + } + + { + [[maybe_unused]] ValueTypeInput val = {}; + } + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer.value/ctor.iter.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer.value/ctor.iter.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer.value/ctor.iter.pass.cpp @@ -0,0 +1,48 @@ +//===----------------------------------------------------------------------===// +// +// 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-has-no-incomplete-ranges + +// explicit outer-iterator::value_type::value_type(outer-iterator i) + +#include + +#include +#include "../types.h" + +// Verify that the constructor is `explicit`. +static_assert(!std::is_convertible_v); +static_assert(!std::is_convertible_v); + +constexpr bool test() { + // `View` is a forward range. + { + CopyableView input = "a"; + SplitViewCopyable v(input, "b"); + ValueTypeCopyable val(v.begin()); + assert(val.begin().base() == input.begin()); + } + + // `View` is an input range. + { + InputView input = "a"; + SplitViewInput v(input, 'b'); + ValueTypeInput val(v.begin()); + assert(*val.begin().base() == *input.begin()); + } + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer.value/end.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer.value/end.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer.value/end.pass.cpp @@ -0,0 +1,78 @@ +//===----------------------------------------------------------------------===// +// +// 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-has-no-incomplete-ranges + +// std::ranges::lazy_split_view::outer-iterator::value_type::end() + +#include + +#include +#include "../types.h" + +constexpr bool test() { + // `View` is a forward range. + { + CopyableView input("a"); + + // Non-const. + { + SplitViewCopyable v(input, "b"); + auto val = *v.begin(); + + static_assert(std::same_as); + static_assert(noexcept(val.end())); + [[maybe_unused]] auto e = val.end(); + } + + // Const. + { + SplitViewCopyable v(input, "b"); + const auto val = *v.begin(); + + static_assert(std::same_as); + static_assert(noexcept(val.end())); + [[maybe_unused]] auto e = val.end(); + } + } + + // `View` is an input range. + { + InputView input("a"); + + // Non-const. + { + SplitViewInput v(input, 'b'); + auto val = *v.begin(); + + static_assert(std::same_as); + static_assert(noexcept(val.end())); + [[maybe_unused]] auto e = val.end(); + } + + // Const. + { + SplitViewInput v(input, 'b'); + const auto val = *v.begin(); + + static_assert(std::same_as); + static_assert(noexcept(val.end())); + [[maybe_unused]] auto e = val.end(); + } + } + + return true; +} + +int main(int, char**) { + assert(test()); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer.value/view_interface.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer.value/view_interface.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer.value/view_interface.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-has-no-incomplete-ranges + +// class std::ranges::lazy_split_view::outer-iterator::value_type; + +#include + +#include +#include +#include "../types.h" + +using V = ValueTypeForward; +static_assert(std::ranges::forward_range); +static_assert(std::ranges::view); + +static_assert(std::is_base_of_v, ValueTypeForward>); + +constexpr bool test() { + // empty() + { + { + SplitViewForward v("abc def", " "); + auto val = *v.begin(); + assert(!val.empty()); + } + + { + SplitViewForward v; + auto val = *v.begin(); + assert(val.empty()); + } + } + + // operator bool() + { + { + SplitViewForward v("abc def", " "); + auto val = *v.begin(); + assert(val); + } + + { + SplitViewForward v; + auto val = *v.begin(); + assert(!val); + } + } + + // front() + { + SplitViewForward v("abc def", " "); + auto val = *v.begin(); + assert(val.front() == 'a'); + } + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer/ctor.copy.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer/ctor.copy.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer/ctor.copy.pass.cpp @@ -0,0 +1,54 @@ +//===----------------------------------------------------------------------===// +// +// 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-has-no-incomplete-ranges + +// constexpr outer-iterator(outer-iterator i) +// requires Const && convertible_to, iterator_t> + +#include + +#include +#include +#include +#include "../types.h" + +// outer-iterator + +template +concept IsConstOuterIter = requires (Iter i) { + { *(*i).begin() } -> std::same_as; +}; +static_assert( IsConstOuterIter); + +static_assert( std::convertible_to< + std::ranges::iterator_t, std::ranges::iterator_t>); + +// outer-iterator + +template +concept IsNonConstOuterIter = requires (Iter i) { + { *(*i).begin() } -> std::same_as; +}; +static_assert( IsNonConstOuterIter); + +static_assert(!std::is_constructible_v); + +constexpr bool test() { + [[maybe_unused]] OuterIterConst i(OuterIterNonConst{}); + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer/ctor.default.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer/ctor.default.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer/ctor.default.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-has-no-incomplete-ranges + +// std::ranges::lazy_split_view::outer-iterator::outer-iterator() + +#include + +#include "../types.h" + +constexpr bool test() { + // `View` is a forward range. + { + [[maybe_unused]] OuterIterForward i; + } + + { + [[maybe_unused]] OuterIterForward i = {}; + } + + // `View` is an input range. + { + [[maybe_unused]] OuterIterInput i; + } + + { + [[maybe_unused]] OuterIterInput i = {}; + } + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer/ctor.parent.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer/ctor.parent.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer/ctor.parent.pass.cpp @@ -0,0 +1,40 @@ +//===----------------------------------------------------------------------===// +// +// 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-has-no-incomplete-ranges + +// explicit std::ranges::lazy_split_view::outer-iterator::outer-iterator(Parent& parent) +// requires (!forward_range) + +#include + +#include +#include +#include "../types.h" + +// Verify that the constructor is `explicit`. +static_assert(!std::is_convertible_v); + +static_assert( std::ranges::forward_range); +static_assert(!std::is_constructible_v); + +constexpr bool test() { + InputView input; + SplitViewInput v(input, ForwardTinyView()); + [[maybe_unused]] OuterIterInput i(v); + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer/ctor.parent_base.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer/ctor.parent_base.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer/ctor.parent_base.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-has-no-incomplete-ranges + +// constexpr outer-iterator(Parent& parent, iterator_t current); +// requires forward_range + +#include + +#include +#include +#include "../types.h" + +static_assert(!std::ranges::forward_range); +static_assert(!std::is_constructible_v>); + +constexpr bool test() { + ForwardView input("abc"); + SplitViewForward v(std::move(input), " "); + [[maybe_unused]] OuterIterForward i(v, input.begin()); + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer/deref.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer/deref.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer/deref.pass.cpp @@ -0,0 +1,58 @@ +//===----------------------------------------------------------------------===// +// +// 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-has-no-incomplete-ranges + +// constexpr outer-iterator::value-type outer-iterator::operator*() const; + +#include + +#include +#include +#include +#include "../small_string.h" +#include "../types.h" + +template +constexpr void test_one(Separator sep) { + using namespace std::string_view_literals; + + View v("abc def ghi"sv, sep); + + // Non-const iterator. + { + auto i = v.begin(); + static_assert(!std::is_reference_v); + assert(SmallString(*i) == "abc"_str); + assert(SmallString(*(++i)) == "def"_str); + assert(SmallString(*(++i)) == "ghi"_str); + } + + // Const iterator. + { + const auto ci = v.begin(); + static_assert(!std::is_reference_v); + assert(SmallString(*ci) == "abc"_str); + } +} + +constexpr bool test() { + // `View` is a forward range. + test_one(" "); + test_one(' '); + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer/equal.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer/equal.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer/equal.pass.cpp @@ -0,0 +1,89 @@ +//===----------------------------------------------------------------------===// +// +// 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-has-no-incomplete-ranges + +// friend constexpr bool operator==(const outer-iterator& x, const outer-iterator& y) +// requires forward_range; +// +// friend constexpr bool operator==(const outer-iterator& x, default_sentinel_t); + +#include + +#include +#include +#include "../types.h" + +template +concept CanCallEquals = requires(const Iter& i) { + i == i; + i != i; +}; + +constexpr bool test() { + // Forward range supports both overloads of `operator==`. + { + // outer-iterator == outer-iterator + { + SplitViewForward v("abc def", " "); + auto b = v.begin(), e = v.end(); + + assert(b == b); + assert(!(b != b)); + + assert(e == e); + assert(!(e != e)); + + assert(!(b == e)); + assert(b != e); + } + + // outer-iterator == default_sentinel + { + SplitViewForward v("abc def", " "); + auto b = v.begin(), e = v.end(); + + assert(!(b == std::default_sentinel)); + assert(b != std::default_sentinel); + assert(e == std::default_sentinel); + assert(!(e != std::default_sentinel)); + } + + // Default-constructed `outer-iterator`s compare equal. + { + OuterIterForward i1, i2; + assert(i1 == i2); + assert(!(i1 != i2)); + } + } + + // Input range only supports comparing an `outer-iterator` to the default sentinel. + { + using namespace std::string_view_literals; + SplitViewInput v("abc def"sv, ' '); + auto b = v.begin(); + std::same_as decltype(auto) e = v.end(); + + static_assert(!CanCallEquals); + + assert(!(b == std::default_sentinel)); + assert(b != std::default_sentinel); + assert(!(b == e)); + assert(b != e); + } + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer/increment.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer/increment.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer/increment.pass.cpp @@ -0,0 +1,86 @@ +//===----------------------------------------------------------------------===// +// +// 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-has-no-incomplete-ranges + +// constexpr outer-iterator& outer-iterator::operator++(); +// constexpr decltype(auto) outer-iterator::operator++(int); + +// Note that corner cases are tested in `range.lazy.split/general.pass.cpp`. + +#include + +#include +#include +#include "../small_string.h" +#include "../types.h" + +constexpr bool test() { + // Can call `outer-iterator::operator++`; `View` is a forward range. + { + SplitViewForward v("abc def ghi", " "); + + // ++i + { + auto i = v.begin(); + assert(*i == "abc"_str); + + decltype(auto) i2 = ++i; + static_assert(std::is_lvalue_reference_v); + assert(&i2 == &i); + assert(*i2 == "def"_str); + } + + // i++ + { + auto i = v.begin(); + assert(*i == "abc"_str); + + decltype(auto) i2 = i++; + static_assert(!std::is_reference_v); + assert(*i2 == "abc"_str); + assert(*i == "def"_str); + } + } + + // Can call `outer-iterator::operator++`; `View` is an input range. + { + SplitViewInput v("abc def ghi", ' '); + + // ++i + { + auto i = v.begin(); + assert(*i == "abc"_str); + + decltype(auto) i2 = ++i; + static_assert(std::is_lvalue_reference_v); + assert(&i2 == &i); + assert(*i2 == "def"_str); + } + + // i++ + { + auto i = v.begin(); + assert(*i == "abc"_str); + + static_assert(std::is_void_v); + i++; + assert(*i == "def"_str); + } + } + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer/types.compile.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer/types.compile.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer/types.compile.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-has-no-incomplete-ranges + +// using iterator_category = input_iterator_tag; // Only defined if `View` is a forward range. +// using iterator_concept = conditional_t, forward_iterator_tag, input_iterator_tag>; +// using difference_type = range_difference_t; + +#include + +#include +#include +#include "../types.h" + +template +using OuterIter = decltype(std::declval>().begin()); + +// iterator_category + +static_assert(std::same_as::iterator_category, std::input_iterator_tag>); + +template +concept NoIteratorCategory = !requires { typename OuterIter::iterator_category; }; +static_assert(NoIteratorCategory); + +// iterator_concept + +static_assert(std::same_as::iterator_concept, std::forward_iterator_tag>); +static_assert(std::same_as::iterator_concept, std::input_iterator_tag>); + +// difference_type + +static_assert(std::same_as::difference_type, + std::ranges::range_difference_t>); diff --git a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/small_string.h b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/small_string.h new file mode 100644 --- /dev/null +++ b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/small_string.h @@ -0,0 +1,79 @@ +//===----------------------------------------------------------------------===// +// +// 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_LAZY_SPLIT_SMALL_STRING_H +#define TEST_STD_RANGES_RANGE_ADAPTORS_RANGE_LAZY_SPLIT_SMALL_STRING_H + +#include +#include +#include +#include + +// A constexpr-friendly lightweight string, primarily useful for comparisons. +// Unlike `std::string`, all functions are `constexpr`. Unlike `std::string_view`, it copies the given string into an +// internal buffer and can work with non-contiguous inputs. +// +// TODO(var-const): remove once https://reviews.llvm.org/D110598 lands and `std::string` can be used instead of this +// class. +template +class BasicSmallString { + constexpr static int N = 32; + Char buffer_[N] = {}; + size_t size_ = 0; + +public: + // Main constructors. + + constexpr BasicSmallString() = default; + + constexpr BasicSmallString(std::basic_string_view v) : size_(v.size()) { + assert(size_ < N); + if (size_ == 0) return; + + std::copy(v.begin(), v.end(), buffer_); + } + + template + constexpr BasicSmallString(I b, const S& e) { + for (; b != e; ++b) { + buffer_[size_++] = *b; + assert(size_ < N); + } + } + + // Delegating constructors. + + constexpr BasicSmallString(const Char* ptr, size_t size) : BasicSmallString(std::basic_string_view(ptr, size)) { + } + + template + constexpr BasicSmallString(R&& from) : BasicSmallString(from.begin(), from.end()) { + } + + // Iterators. + + constexpr Char* begin() { return buffer_; } + constexpr Char* end() { return buffer_ + size_; } + constexpr const Char* begin() const { return buffer_; } + constexpr const Char* end() const { return buffer_ + size_; } + + friend constexpr bool operator==(const BasicSmallString& lhs, const BasicSmallString& rhs) { + return lhs.size_ == rhs.size_ && std::equal(lhs.buffer_, lhs.buffer_ + lhs.size_, rhs.buffer_); + } + friend constexpr bool operator==(const BasicSmallString& lhs, std::string_view rhs) { + return lhs == BasicSmallString(rhs); + } +}; + +using SmallString = BasicSmallString; + +inline constexpr SmallString operator "" _str(const char* ptr, size_t size) { + return SmallString(ptr, size); +} + +#endif // TEST_STD_RANGES_RANGE_ADAPTORS_RANGE_LAZY_SPLIT_SMALL_STRING_H diff --git a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/types.h b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/types.h new file mode 100644 --- /dev/null +++ b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/types.h @@ -0,0 +1,202 @@ +//===----------------------------------------------------------------------===// +// +// 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_LAZY_SPLIT_TYPES_H +#define TEST_STD_RANGES_RANGE_ADAPTORS_RANGE_LAZY_SPLIT_TYPES_H + +#include +#include +#include +#include +#include "small_string.h" +#include "test_macros.h" +#include "test_iterators.h" + +// CopyableView + +struct CopyableView : std::ranges::view_base { + std::string_view view_; + constexpr explicit CopyableView() = default; + constexpr CopyableView(const char* ptr) : view_(ptr) {} + constexpr CopyableView(std::string_view v) : view_(v) {} + constexpr forward_iterator begin() const { return forward_iterator(view_.begin()); } + constexpr forward_iterator end() const { return forward_iterator(view_.end()); } + constexpr bool operator==(const CopyableView& rhs) const { return view_ == rhs.view_; } +}; +static_assert( std::ranges::forward_range); +static_assert( std::ranges::forward_range); +static_assert( std::ranges::view); +static_assert( std::is_copy_constructible_v); + +// ForwardView + +struct ForwardView : std::ranges::view_base { + std::string_view view_; + constexpr explicit ForwardView() = default; + constexpr ForwardView(const char* ptr) : view_(ptr) {} + constexpr ForwardView(std::string_view v) : view_(v) {} + constexpr ForwardView(ForwardView&&) = default; + constexpr ForwardView& operator=(ForwardView&&) = default; + constexpr forward_iterator begin() const { return forward_iterator(view_.begin()); } + constexpr forward_iterator end() const { return forward_iterator(view_.end()); } +}; +static_assert( std::ranges::forward_range); +static_assert( std::ranges::forward_range); +static_assert( std::ranges::view); +static_assert(!std::is_copy_constructible_v); +static_assert( std::is_move_constructible_v); + +// ForwardDiffView + +// Iterator types differ based on constness of this class. +struct ForwardDiffView : std::ranges::view_base { + SmallString buffer_; + constexpr explicit ForwardDiffView() = default; + constexpr ForwardDiffView(const char* ptr) : ForwardDiffView(std::string_view(ptr)) {} + constexpr ForwardDiffView(std::string_view v) : buffer_(v) {} + constexpr ForwardDiffView(ForwardDiffView&&) = default; + constexpr ForwardDiffView& operator=(ForwardDiffView&&) = default; + constexpr ForwardDiffView(const ForwardDiffView&) = default; + constexpr ForwardDiffView& operator=(const ForwardDiffView&) = default; + constexpr forward_iterator begin() { return forward_iterator(buffer_.begin()); } + constexpr forward_iterator end() { return forward_iterator(buffer_.end()); } + constexpr forward_iterator begin() const { return forward_iterator(buffer_.begin()); } + constexpr forward_iterator end() const { return forward_iterator(buffer_.end()); } +}; +static_assert( std::ranges::forward_range); +static_assert( std::ranges::forward_range); +static_assert( std::ranges::view); +static_assert(!std::same_as, std::ranges::iterator_t>); + +// ForwardOnlyIfNonConstView + +template +class almost_forward_iterator { + It it_; + + template friend class almost_forward_iterator; + +public: + using iterator_category = std::forward_iterator_tag; + using value_type = typename std::iterator_traits::value_type; + using difference_type = typename std::iterator_traits::difference_type; + using pointer = It; + using reference = typename std::iterator_traits::reference; + + constexpr almost_forward_iterator() : it_() {} + constexpr explicit almost_forward_iterator(It it) : it_(it) {} + template + constexpr almost_forward_iterator(const almost_forward_iterator& u) : it_(u.it_) {} + + constexpr reference operator*() const { return *it_; } + constexpr pointer operator->() const { return it_; } + + constexpr almost_forward_iterator& operator++() { ++it_; return *this; } + // Notice the slightly different return type. + constexpr const almost_forward_iterator operator++(int) { return almost_forward_iterator(it_); } + + friend constexpr bool operator==(const almost_forward_iterator& x, const almost_forward_iterator& y) { + return x.it_ == y.it_; + } + friend constexpr bool operator!=(const almost_forward_iterator& x, const almost_forward_iterator& y) { + return x.it_ != y.it_; + } +}; +static_assert(!std::forward_iterator>); +static_assert( std::input_iterator>); + +struct ForwardOnlyIfNonConstView : std::ranges::view_base { + std::string_view view_; + + constexpr explicit ForwardOnlyIfNonConstView() = default; + constexpr ForwardOnlyIfNonConstView(const char* ptr) : view_(ptr) {} + constexpr ForwardOnlyIfNonConstView(std::string_view v) : view_(v) {} + constexpr ForwardOnlyIfNonConstView(ForwardOnlyIfNonConstView&&) = default; + constexpr ForwardOnlyIfNonConstView& operator=(ForwardOnlyIfNonConstView&&) = default; + + constexpr forward_iterator begin() { return forward_iterator(view_.begin()); } + constexpr forward_iterator end() { return forward_iterator(view_.end()); } + constexpr almost_forward_iterator begin() const { + return almost_forward_iterator(view_.begin()); + } + constexpr almost_forward_iterator end() const { + return almost_forward_iterator(view_.end()); + } +}; +static_assert( std::ranges::forward_range); +static_assert(!std::ranges::forward_range); +static_assert( std::ranges::view); + +// InputView + +struct InputView : std::ranges::view_base { + SmallString buffer_; + + constexpr InputView() = default; + constexpr InputView(const char* s) : InputView(std::string_view(s)) {} + constexpr InputView(std::string_view v) : buffer_(v) {} + + constexpr cpp20_input_iterator begin() { return cpp20_input_iterator(buffer_.begin()); } + constexpr sentinel_wrapper> end() { + return sentinel_wrapper(cpp20_input_iterator(buffer_.end())); + } + constexpr cpp20_input_iterator begin() const { + return cpp20_input_iterator(buffer_.begin()); + } + constexpr sentinel_wrapper> end() const { + return sentinel_wrapper(cpp20_input_iterator(buffer_.end())); + } +}; + +static_assert(std::ranges::input_range); +static_assert(std::ranges::input_range); +static_assert(std::ranges::view); + +// ForwardTinyView + +struct ForwardTinyView : std::ranges::view_base { + char c_[1] = {}; + constexpr ForwardTinyView() = default; + constexpr ForwardTinyView(char c) { *c_ = c; } + constexpr forward_iterator begin() const { return forward_iterator(c_); } + constexpr forward_iterator end() const { return forward_iterator(c_ + 1); } + constexpr static size_t size() { return 1; } +}; +static_assert(std::ranges::forward_range); +static_assert(std::ranges::view); +LIBCPP_STATIC_ASSERT(std::ranges::__tiny_range); + +// Aliases + +using SplitViewCopyable = std::ranges::lazy_split_view; +using OuterIterCopyable = std::ranges::iterator_t; +using ValueTypeCopyable = OuterIterCopyable::value_type; +using InnerIterCopyable = std::ranges::iterator_t; +using BaseIterCopyable = std::ranges::iterator_t; + +using SplitViewForward = std::ranges::lazy_split_view; +using OuterIterForward = std::ranges::iterator_t; +using ValueTypeForward = OuterIterForward::value_type; +using InnerIterForward = std::ranges::iterator_t; +using BaseIterForward = std::ranges::iterator_t; + +using SplitViewInput = std::ranges::lazy_split_view; +using OuterIterInput = std::ranges::iterator_t; +using ValueTypeInput = OuterIterInput::value_type; +using InnerIterInput = std::ranges::iterator_t; +using BaseIterInput = std::ranges::iterator_t; + +using SplitViewDiff = std::ranges::lazy_split_view; +using OuterIterConst = decltype(std::declval().begin()); +using OuterIterNonConst = decltype(std::declval().begin()); +static_assert(!std::same_as); +using InnerIterConst = decltype((*std::declval()).begin()); +using InnerIterNonConst = decltype((*std::declval()).begin()); +static_assert(!std::same_as); + +#endif // TEST_STD_RANGES_RANGE_ADAPTORS_RANGE_LAZY_SPLIT_TYPES_H diff --git a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/view_interface.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/view_interface.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/view_interface.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 +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: libcpp-has-no-incomplete-ranges + +// class std::ranges::lazy_split_view; + +#include + +#include +#include +#include +#include +#include "types.h" + +using V = SplitViewForward; + +static_assert(std::is_base_of_v, SplitViewForward>); + +constexpr bool test() { + using namespace std::string_view_literals; + + // empty() + { + { + std::ranges::lazy_split_view v("abc def", " "); + assert(!v.empty()); + } + + { + // Note: an empty string literal would still produce a non-empty output because the terminating zero is treated as + // a separate character; hence the use of `string_view`. + std::ranges::lazy_split_view v(""sv, ""); + assert(v.empty()); + } + } + + // operator bool() + { + { + std::ranges::lazy_split_view v("abc", ""); + assert(v); + } + + { + // Note: an empty string literal would still produce a non-empty output because the terminating zero is treated as + // a separate character; hence the use of `string_view`. + std::ranges::lazy_split_view v(""sv, ""); + assert(!v); + } + } + + // front() + { + SplitViewForward v("abc", ""); + assert(*(v.front()).begin() == 'a'); + } + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/support/test_iterators.h b/libcxx/test/support/test_iterators.h --- a/libcxx/test/support/test_iterators.h +++ b/libcxx/test/support/test_iterators.h @@ -506,6 +506,7 @@ template void operator,(T const &) = delete; }; +static_assert(std::input_iterator>); template struct iter_value_or_void { using type = void; };