diff --git a/stl/inc/algorithm b/stl/inc/algorithm index 9e0d2458f7d..fdea68f832b 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -499,58 +499,6 @@ template _Se, class _Pj = identity, - indirect_unary_predicate> _Pr> - _NODISCARD constexpr _It operator()(_It _First, _Se _Last, _Pr _Pred, _Pj _Proj = {}) const { - _Adl_verify_range(_First, _Last); - - auto _UResult = _Find_if_not_unchecked( - _Get_unwrapped(_STD move(_First)), _Get_unwrapped(_STD move(_Last)), _Pass_fn(_Pred), _Pass_fn(_Proj)); - - _Seek_wrapped(_First, _STD move(_UResult)); - return _First; - } - - template , _Pj>> _Pr> - _NODISCARD constexpr borrowed_iterator_t<_Rng> operator()(_Rng&& _Range, _Pr _Pred, _Pj _Proj = {}) const { - auto _First = _RANGES begin(_Range); - - auto _UResult = _Find_if_not_unchecked( - _Get_unwrapped(_STD move(_First)), _Uend(_Range), _Pass_fn(_Pred), _Pass_fn(_Proj)); - - _Seek_wrapped(_First, _STD move(_UResult)); - return _First; - } - - private: - template - _NODISCARD static constexpr _It _Find_if_not_unchecked(_It _First, const _Se _Last, _Pr _Pred, _Pj _Proj) { - _STL_INTERNAL_STATIC_ASSERT(input_iterator<_It>); - _STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se, _It>); - _STL_INTERNAL_STATIC_ASSERT(indirect_unary_predicate<_Pr, projected<_It, _Pj>>); - - for (; _First != _Last; ++_First) { - if (!_STD invoke(_Pred, _STD invoke(_Proj, *_First))) { - break; - } - } - - return _First; - } - }; - - inline constexpr _Find_if_not_fn find_if_not{_Not_quite_object::_Construct_tag{}}; -} // namespace ranges -#endif // __cpp_lib_concepts - // FUNCTION TEMPLATE adjacent_find template _NODISCARD _CONSTEXPR20 _FwdIt adjacent_find(const _FwdIt _First, _FwdIt _Last, _Pr _Pred) { diff --git a/stl/inc/ranges b/stl/inc/ranges index 776e1310efe..e0e0530b67f 100644 --- a/stl/inc/ranges +++ b/stl/inc/ranges @@ -1915,6 +1915,104 @@ namespace ranges { inline constexpr _Drop_fn drop; } // namespace views + // CLASS TEMPLATE ranges::drop_while_view + // clang-format off + template + requires input_range<_Vw> && is_object_v<_Pr> && indirect_unary_predicate> + class drop_while_view : public _Cached_position_t, _Vw, drop_while_view<_Vw, _Pr>> { + // clang-format on + private: + /* [[no_unique_address]] */ _Vw _Range{}; + /* [[no_unique_address]] */ _Semiregular_box<_Pr> _Pred{}; + + public: + drop_while_view() = default; + + constexpr drop_while_view(_Vw _Range_, _Pr _Pred_) noexcept( + is_nothrow_move_constructible_v<_Vw>&& is_nothrow_move_constructible_v<_Pr>) // strengthened + : _Range(_STD move(_Range_)), _Pred{in_place, _STD move(_Pred_)} {} + + _NODISCARD constexpr _Vw base() const& noexcept( + is_nothrow_copy_constructible_v<_Vw>) /* strengthened */ requires copy_constructible<_Vw> { + return _Range; + } + _NODISCARD constexpr _Vw base() && noexcept(is_nothrow_move_constructible_v<_Vw>) /* strengthened */ { + return _STD move(_Range); + } + + _NODISCARD constexpr const _Pr& pred() const noexcept /* strengthened */ { +#if _CONTAINER_DEBUG_LEVEL > 0 + _STL_VERIFY(_Pred, "value-initialized drop_while_view has no predicate"); +#endif // _CONTAINER_DEBUG_LEVEL > 0 + return *_Pred; + } + + _NODISCARD constexpr auto begin() { +#if _CONTAINER_DEBUG_LEVEL > 0 + _STL_VERIFY(_Pred, "LWG-3490 forbids calling begin on a drop_while_view with no predicate"); +#endif // _CONTAINER_DEBUG_LEVEL > 0 + if constexpr (forward_range<_Vw>) { + if (this->_Has_cache()) { + return this->_Get_cache(_Range); + } + } + + auto _First = _RANGES find_if_not(_Range, _STD cref(*_Pred)); + if constexpr (forward_range<_Vw>) { + this->_Set_cache(_Range, _First); + } + + return _First; + } + + _NODISCARD constexpr auto end() noexcept(noexcept(_RANGES end(_Range))) /* strengthened */ { + return _RANGES end(_Range); + } + }; + + template + drop_while_view(_Rng&&, _Pr) -> drop_while_view, _Pr>; + + namespace views { + // VARIABLE views::drop_while + class _Drop_while_fn { + private: + template + struct _Partial : _Pipe::_Base<_Partial<_Pr>> { + /* [[no_unique_address]] */ _Semiregular_box<_Pr> _Pred; + + template + _NODISCARD constexpr auto operator()(_Rng&& _Range) const& noexcept( + noexcept(drop_while_view{_STD forward<_Rng>(_Range), *_Pred})) requires requires { + drop_while_view{static_cast<_Rng&&>(_Range), *_Pred}; + } + { return drop_while_view{_STD forward<_Rng>(_Range), *_Pred}; } + + template + _NODISCARD constexpr auto operator()(_Rng&& _Range) && noexcept( + noexcept(drop_while_view{_STD forward<_Rng>(_Range), _STD move(*_Pred)})) requires requires { + drop_while_view{static_cast<_Rng&&>(_Range), _STD move(*_Pred)}; + } + { return drop_while_view{_STD forward<_Rng>(_Range), _STD move(*_Pred)}; } + }; + + public: + template + _NODISCARD constexpr auto operator()(_Rng&& _Range, _Pr _Pred) const + noexcept(noexcept(drop_while_view{_STD forward<_Rng>(_Range), _STD move(_Pred)})) requires requires { + drop_while_view{static_cast<_Rng&&>(_Range), _STD move(_Pred)}; + } + { return drop_while_view{_STD forward<_Rng>(_Range), _STD move(_Pred)}; } + + template <_Copy_constructible_object _Pr> + _NODISCARD constexpr auto operator()(_Pr _Pred) const noexcept(is_nothrow_move_constructible_v<_Pr>) { + return _Partial<_Pr>{._Pred = {in_place, _STD move(_Pred)}}; + } + }; + + inline constexpr _Drop_while_fn drop_while; + } // namespace views + // CLASS TEMPLATE ranges::reverse_view // clang-format off template diff --git a/stl/inc/xutility b/stl/inc/xutility index 7dbdc5aa5b6..a1c5c9a627b 100644 --- a/stl/inc/xutility +++ b/stl/inc/xutility @@ -5849,6 +5849,54 @@ namespace ranges { }; inline constexpr _Find_if_fn find_if{_Not_quite_object::_Construct_tag{}}; + + // VARIABLE ranges::find_if_not + class _Find_if_not_fn : private _Not_quite_object { + public: + using _Not_quite_object::_Not_quite_object; + + template _Se, class _Pj = identity, + indirect_unary_predicate> _Pr> + _NODISCARD constexpr _It operator()(_It _First, _Se _Last, _Pr _Pred, _Pj _Proj = {}) const { + _Adl_verify_range(_First, _Last); + + auto _UResult = _Find_if_not_unchecked( + _Get_unwrapped(_STD move(_First)), _Get_unwrapped(_STD move(_Last)), _Pass_fn(_Pred), _Pass_fn(_Proj)); + + _Seek_wrapped(_First, _STD move(_UResult)); + return _First; + } + + template , _Pj>> _Pr> + _NODISCARD constexpr borrowed_iterator_t<_Rng> operator()(_Rng&& _Range, _Pr _Pred, _Pj _Proj = {}) const { + auto _First = _RANGES begin(_Range); + + auto _UResult = _Find_if_not_unchecked( + _Get_unwrapped(_STD move(_First)), _Uend(_Range), _Pass_fn(_Pred), _Pass_fn(_Proj)); + + _Seek_wrapped(_First, _STD move(_UResult)); + return _First; + } + + private: + template + _NODISCARD static constexpr _It _Find_if_not_unchecked(_It _First, const _Se _Last, _Pr _Pred, _Pj _Proj) { + _STL_INTERNAL_STATIC_ASSERT(input_iterator<_It>); + _STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se, _It>); + _STL_INTERNAL_STATIC_ASSERT(indirect_unary_predicate<_Pr, projected<_It, _Pj>>); + + for (; _First != _Last; ++_First) { + if (!_STD invoke(_Pred, _STD invoke(_Proj, *_First))) { + break; + } + } + + return _First; + } + }; + + inline constexpr _Find_if_not_fn find_if_not{_Not_quite_object::_Construct_tag{}}; } // namespace ranges #endif // __cpp_lib_concepts diff --git a/tests/std/test.lst b/tests/std/test.lst index ce4aff5bb8d..2d7ad09a685 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -340,6 +340,8 @@ tests\P0896R4_ranges_to_address tests\P0896R4_stream_iterators tests\P0896R4_views_all tests\P0896R4_views_drop +tests\P0896R4_views_drop_while +tests\P0896R4_views_drop_while_death tests\P0896R4_views_empty tests\P0896R4_views_filter tests\P0896R4_views_filter_death diff --git a/tests/std/tests/P0896R4_ranges_range_machinery/test.cpp b/tests/std/tests/P0896R4_ranges_range_machinery/test.cpp index 1605cb5ebbe..662c3caffaa 100644 --- a/tests/std/tests/P0896R4_ranges_range_machinery/test.cpp +++ b/tests/std/tests/P0896R4_ranges_range_machinery/test.cpp @@ -91,6 +91,7 @@ STATIC_ASSERT(test_cpo(ranges::cdata)); STATIC_ASSERT(test_cpo(ranges::views::all)); STATIC_ASSERT(test_cpo(ranges::views::drop)); +STATIC_ASSERT(test_cpo(ranges::views::drop_while)); STATIC_ASSERT(test_cpo(ranges::views::filter)); STATIC_ASSERT(test_cpo(ranges::views::reverse)); STATIC_ASSERT(test_cpo(ranges::views::single)); diff --git a/tests/std/tests/P0896R4_views_drop_while/env.lst b/tests/std/tests/P0896R4_views_drop_while/env.lst new file mode 100644 index 00000000000..f3ccc8613c6 --- /dev/null +++ b/tests/std/tests/P0896R4_views_drop_while/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\concepts_matrix.lst diff --git a/tests/std/tests/P0896R4_views_drop_while/test.cpp b/tests/std/tests/P0896R4_views_drop_while/test.cpp new file mode 100644 index 00000000000..ab834b012a7 --- /dev/null +++ b/tests/std/tests/P0896R4_views_drop_while/test.cpp @@ -0,0 +1,431 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +using namespace std; + +// Test a silly precomposed range adaptor pipeline +template +constexpr auto is_less_than = [](const auto& x) { return x < X; }; + +using Pred = remove_const_t)>; +STATIC_ASSERT(is_nothrow_copy_constructible_v&& is_nothrow_move_constructible_v); + +constexpr auto pipeline = views::drop_while(is_less_than<3>) | views::drop_while(is_less_than<3>) + | views::drop_while(is_less_than<3>) | views::drop_while(is_less_than<3>); + +template > +using pipeline_t = ranges::drop_while_view< + ranges::drop_while_view, Pred>, Pred>, Pred>; + +template +concept CanViewDropWhile = requires(Rng&& r) { + views::drop_while(static_cast(r), is_less_than<3>); +}; + +template +constexpr bool test_one(Rng&& rng, Expected&& expected) { + using ranges::drop_while_view, ranges::bidirectional_range, ranges::common_range, ranges::contiguous_range, + ranges::enable_borrowed_range, ranges::forward_range, ranges::iterator_t, ranges::prev, + ranges::random_access_range; + + constexpr bool is_view = ranges::view>; + + using V = views::all_t; + using R = drop_while_view; + STATIC_ASSERT(ranges::view); + STATIC_ASSERT(ranges::input_range); + STATIC_ASSERT(forward_range == forward_range); + STATIC_ASSERT(bidirectional_range == bidirectional_range); + STATIC_ASSERT(random_access_range == random_access_range); + STATIC_ASSERT(contiguous_range == contiguous_range); + + // Validate range adaptor object and range adaptor closure + constexpr auto closure = views::drop_while(is_less_than<3>); + + // ... with lvalue argument + STATIC_ASSERT(CanViewDropWhile == (!is_view || copyable) ); + if constexpr (CanViewDropWhile) { // Validate lvalue + constexpr bool is_noexcept = !is_view || is_nothrow_copy_constructible_v; + + STATIC_ASSERT(same_as)), R>); + STATIC_ASSERT(noexcept(views::drop_while(rng, is_less_than<3>)) == is_noexcept); + + STATIC_ASSERT(same_as); + STATIC_ASSERT(noexcept(rng | closure) == is_noexcept); + + STATIC_ASSERT(same_as>); + STATIC_ASSERT(noexcept(rng | pipeline) == is_noexcept); + } + + // ... with const lvalue argument + STATIC_ASSERT(CanViewDropWhile&> == (!is_view || copyable) ); + if constexpr (is_view && copyable) { + constexpr bool is_noexcept = is_nothrow_copy_constructible_v; + + STATIC_ASSERT(same_as)), R>); + STATIC_ASSERT(noexcept(views::drop_while(as_const(rng), is_less_than<3>)) == is_noexcept); + + STATIC_ASSERT(same_as); + STATIC_ASSERT(noexcept(as_const(rng) | closure) == is_noexcept); + + STATIC_ASSERT(same_as&>>); + STATIC_ASSERT(noexcept(as_const(rng) | pipeline) == is_noexcept); + } else if constexpr (!is_view) { + using RC = drop_while_view&>, Pred>; + constexpr bool is_noexcept = + is_nothrow_constructible_v&, decltype((is_less_than<3>) )>; + + STATIC_ASSERT(same_as)), RC>); + STATIC_ASSERT(noexcept(views::drop_while(as_const(rng), is_less_than<3>)) == is_noexcept); + + STATIC_ASSERT(same_as); + STATIC_ASSERT(noexcept(as_const(rng) | closure) == is_noexcept); + + STATIC_ASSERT(same_as&>>); + STATIC_ASSERT(noexcept(as_const(rng) | pipeline) == is_noexcept); + } + + // ... with rvalue argument + STATIC_ASSERT(CanViewDropWhile> == is_view || enable_borrowed_range>); + if constexpr (is_view) { + constexpr bool is_noexcept = is_nothrow_move_constructible_v; + STATIC_ASSERT(same_as)), R>); + STATIC_ASSERT(noexcept(views::drop_while(move(rng), is_less_than<3>)) == is_noexcept); + + STATIC_ASSERT(same_as); + STATIC_ASSERT(noexcept(move(rng) | closure) == is_noexcept); + + STATIC_ASSERT(same_as>>); + STATIC_ASSERT(noexcept(move(rng) | pipeline) == is_noexcept); + } else if constexpr (enable_borrowed_range>) { + using S = decltype(ranges::subrange{move(rng)}); + using RS = drop_while_view; + constexpr bool is_noexcept = noexcept(S{move(rng)}); + + STATIC_ASSERT(same_as)), RS>); + STATIC_ASSERT(noexcept(views::drop_while(move(rng), is_less_than<3>)) == is_noexcept); + + STATIC_ASSERT(same_as); + STATIC_ASSERT(noexcept(move(rng) | closure) == is_noexcept); + + STATIC_ASSERT(same_as>>); + STATIC_ASSERT(noexcept(move(rng) | pipeline) == is_noexcept); + } + + // ... with const rvalue argument + STATIC_ASSERT(CanViewDropWhile> == (is_view && copyable) + || (!is_view && enable_borrowed_range>) ); + if constexpr (is_view && copyable) { + constexpr bool is_noexcept = is_nothrow_copy_constructible_v; + + STATIC_ASSERT(same_as)), R>); + STATIC_ASSERT(noexcept(views::drop_while(move(as_const(rng)), is_less_than<3>)) == is_noexcept); + + STATIC_ASSERT(same_as); + STATIC_ASSERT(noexcept(move(as_const(rng)) | closure) == is_noexcept); + + STATIC_ASSERT(same_as>>); + STATIC_ASSERT(noexcept(move(as_const(rng)) | pipeline) == is_noexcept); + } else if constexpr (!is_view && enable_borrowed_range>) { + using S = decltype(ranges::subrange{move(as_const(rng))}); + using RS = drop_while_view; + constexpr bool is_noexcept = noexcept(S{move(as_const(rng))}); + + STATIC_ASSERT(same_as)), RS>); + STATIC_ASSERT(noexcept(views::drop_while(move(as_const(rng)), is_less_than<3>)) == is_noexcept); + + STATIC_ASSERT(same_as); + STATIC_ASSERT(noexcept(move(as_const(rng)) | closure) == is_noexcept); + + STATIC_ASSERT(same_as>>); + STATIC_ASSERT(noexcept(move(as_const(rng)) | pipeline) == is_noexcept); + } + + // Validate deduction guide +#if !defined(__clang__) && !defined(__EDG__) // TRANSITION, DevCom-1159442 + (void) 42; +#endif // TRANSITION, DevCom-1159442 + same_as auto r = drop_while_view{forward(rng), is_less_than<3>}; + assert(ranges::equal(r, expected)); + if constexpr (forward_range) { + // drop_while_view memoizes the first iterator, let's repeat a few times for coverage. + assert(ranges::equal(r, expected)); + assert(ranges::equal(r, expected)); + assert(ranges::equal(r, expected)); + } + + { // Validate drop_while_view::pred + [[maybe_unused]] same_as auto pred_copy = as_const(r).pred(); + STATIC_ASSERT(noexcept(as_const(r).pred())); + } + + const bool is_empty = ranges::empty(expected); + + // Validate view_interface::empty and operator bool + STATIC_ASSERT(CanMemberEmpty == forward_range); + STATIC_ASSERT(CanBool == CanEmpty); + if constexpr (CanMemberEmpty) { + assert(r.empty() == is_empty); + assert(static_cast(r) == !is_empty); + } else { + STATIC_ASSERT(CanEmpty == CanSize); + if constexpr (CanEmpty) { + assert(ranges::empty(r) == is_empty); + assert(static_cast(r) == !is_empty); + } + } + + STATIC_ASSERT(!CanEmpty); + STATIC_ASSERT(!CanBool); + + // Validate drop_while_view::begin + STATIC_ASSERT(CanMemberBegin); + if (forward_range) { // intentionally not if constexpr + // Ditto "let's make some extra calls because memoization" + const same_as> auto i = r.begin(); + if (!is_empty) { + assert(*i == *begin(expected)); + assert(*r.begin() == *begin(expected)); + assert(*r.begin() == *begin(expected)); + } + + if constexpr (copyable) { + auto r2 = r; + const same_as> auto i2 = r2.begin(); + if (!is_empty) { + assert(*i2 == *i); + assert(*r2.begin() == *i2); + assert(*r2.begin() == *i2); + } + } + + STATIC_ASSERT(!CanBegin); + } + + // Validate drop_while_view::end + STATIC_ASSERT(CanMemberEnd); + if (!is_empty) { + if constexpr (common_range) { + same_as> auto i = r.end(); + if constexpr (bidirectional_range) { + assert(*prev(i) == *prev(end(expected))); + } + } else { + [[maybe_unused]] same_as> auto s = r.end(); + } + + if constexpr (bidirectional_range && common_range && copyable) { + auto r2 = r; + assert(*prev(r2.end()) == *prev(end(expected))); + } + + STATIC_ASSERT(!CanEnd); + } + + // Validate view_interface::data + STATIC_ASSERT(CanMemberData == contiguous_range); + STATIC_ASSERT(CanData == contiguous_range); + if constexpr (contiguous_range) { + const same_as>*> auto ptr1 = r.data(); + assert(ptr1 == to_address(r.begin())); + } + STATIC_ASSERT(!CanData); + + // Validate view_interface::size + STATIC_ASSERT(CanMemberSize == CanSize); + if constexpr (CanMemberSize) { + assert(r.size() == static_cast(ranges::size(expected))); + } else { + STATIC_ASSERT(!CanSize); + } + + // Validate view_interface::operator[] + STATIC_ASSERT(CanIndex == random_access_range); + if constexpr (CanIndex) { + if (!is_empty) { + assert(r[0] == expected[0]); + } + } + STATIC_ASSERT(!CanIndex); + + // Validate view_interface::front and back + STATIC_ASSERT(CanMemberFront == forward_range); + if constexpr (CanMemberFront) { + if (!is_empty) { + assert(r.front() == *begin(expected)); + } + } + STATIC_ASSERT(!CanMemberFront); + + STATIC_ASSERT(CanMemberBack == (bidirectional_range && common_range) ); + if constexpr (CanMemberBack) { + if (!is_empty) { + assert(r.back() == *prev(end(expected))); + } + } + STATIC_ASSERT(!CanMemberBack); + + // Validate drop_while_view::base() const& + STATIC_ASSERT(CanMemberBase == copy_constructible); + if constexpr (copy_constructible && forward_range) { + same_as auto b1 = as_const(r).base(); + STATIC_ASSERT(noexcept(as_const(r).base()) == is_nothrow_copy_constructible_v); + if (!is_empty) { + assert(*b1.begin() == 0); // NB: depends on the test data + if constexpr (bidirectional_range && common_range) { + assert(*prev(b1.end()) == *prev(end(expected))); + } + } + } + + if (forward_range) { // intentionally not if constexpr + same_as auto b2 = move(r).base(); + STATIC_ASSERT(noexcept(move(r).base()) == is_nothrow_move_constructible_v); + if (!is_empty) { + assert(*b2.begin() == 0); // NB: depends on the test data + if constexpr (bidirectional_range && common_range) { + assert(*prev(b2.end()) == *prev(end(expected))); + } + } + } + return true; +} + +static constexpr int some_ints[] = {0, 1, 2, 3, 4, 3, 2, 1}; +static constexpr int expected_output[] = {3, 4, 3, 2, 1}; +static constexpr int expected_output_reverse[] = {0, 1, 2, 3, 4, 3}; + +struct instantiator { + template + static constexpr void call() { + R r{some_ints}; + test_one(r, expected_output); + } +}; + +template +using test_range = test::range}, IsCommon, + test::CanCompare{derived_from || IsCommon == test::Common::yes}, + test::ProxyRef{!derived_from}>; + +constexpr void instantiation_test() { +#ifdef TEST_EVERYTHING + test_in(); +#else // ^^^ test all input range permutations / test only "interesting" permutations vvv + // The view is sensitive to category and commonality, but oblivious to size, differencing, and proxyness. + using test::Common; + + instantiator::call>(); + instantiator::call>(); + instantiator::call>(); + instantiator::call>(); + instantiator::call>(); + instantiator::call>(); + instantiator::call>(); + instantiator::call>(); + instantiator::call>(); + instantiator::call>(); +#endif // TEST_EVERYTHING +} + +template > +using move_only_view = test::range}, + test::ProxyRef{!derived_from}, test::CanView::yes, test::Copyability::move_only>; + +int main() { + // Validate views + { // ... copyable + constexpr span s{some_ints}; + STATIC_ASSERT(test_one(s, expected_output)); + test_one(s, expected_output); + } + { // ... move-only + test_one(move_only_view{some_ints}, expected_output); + test_one(move_only_view{some_ints}, expected_output); + test_one(move_only_view{some_ints}, expected_output); + test_one(move_only_view{some_ints}, expected_output); + test_one(move_only_view{some_ints}, expected_output); + test_one(move_only_view{some_ints}, expected_output); + test_one(move_only_view{some_ints}, expected_output); + } + + // Validate non-views + { + STATIC_ASSERT(test_one(some_ints, expected_output)); + test_one(some_ints, expected_output); + } + { + vector vec(ranges::begin(some_ints), ranges::end(some_ints)); + test_one(vec, expected_output); + } + { + forward_list lst(ranges::begin(some_ints), ranges::end(some_ints)); + test_one(lst, expected_output); + } + + // Validate a non-view borrowed range + { + constexpr span s{some_ints}; + STATIC_ASSERT(test_one(s, expected_output)); + test_one(s, expected_output); + } + + // drop_while/reverse interaction test + { + auto dwr_pipe = views::drop_while(is_less_than<3>) | views::reverse; + auto rdw_pipe = views::reverse | views::drop_while(is_less_than<3>); + + auto r0 = some_ints | dwr_pipe; + using R0 = decltype(r0); + STATIC_ASSERT(ranges::bidirectional_range && ranges::view); + assert(ranges::equal(r0, views::reverse(expected_output))); + + auto r1 = some_ints | rdw_pipe; + using R1 = decltype(r1); + STATIC_ASSERT(ranges::bidirectional_range && ranges::view); + assert(ranges::equal(r1, views::reverse(expected_output_reverse))); + } + + { // empty range + STATIC_ASSERT(test_one(span{}, span{})); + test_one(span{}, span{}); + } + + STATIC_ASSERT((instantiation_test(), true)); + instantiation_test(); + + { // Validate **non-standard guarantee** that predicates are moved into the range adaptor closure, and into the view + // object from an rvalue closure + struct Fn { + Fn() = default; + Fn(Fn&&) = default; + Fn(const Fn&) { + assert(false); + } + Fn& operator=(Fn&&) = default; + + Fn& operator=(const Fn&) { + assert(false); + return *this; + } + + bool operator()(int) const { + return true; + } + }; + + (void) views::drop_while(Fn{})(span{}); + } +} diff --git a/tests/std/tests/P0896R4_views_drop_while_death/env.lst b/tests/std/tests/P0896R4_views_drop_while_death/env.lst new file mode 100644 index 00000000000..22f1f0230a4 --- /dev/null +++ b/tests/std/tests/P0896R4_views_drop_while_death/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\strict_winsdk_concepts_matrix.lst diff --git a/tests/std/tests/P0896R4_views_drop_while_death/test.cpp b/tests/std/tests/P0896R4_views_drop_while_death/test.cpp new file mode 100644 index 00000000000..feb5c926892 --- /dev/null +++ b/tests/std/tests/P0896R4_views_drop_while_death/test.cpp @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#define _CONTAINER_DEBUG_LEVEL 1 + +#include +#include +#include +#include + +#include +using namespace std; + +[[maybe_unused]] constexpr auto lambda = [x = 42](int) { return x == 42; }; +using DWV = decltype(ranges::drop_while_view{span{}, lambda}); + +void test_view_predicate() { + DWV r; + (void) r.pred(); // value-initialized drop_while_view has no predicate +} + +void test_view_begin() { + DWV r; + (void) r.begin(); // LWG-3490 forbids calling begin on a drop_while_view with no predicate +} + +int main(int argc, char* argv[]) { + std_testing::death_test_executive exec; + + exec.add_death_tests({ + test_view_predicate, + test_view_begin, + }); + + return exec.run(argc, argv); +}