From 7fd6501ece06d4087c40bbde560eec29bf8f98ed Mon Sep 17 00:00:00 2001 From: Michael Schellenberger Costa Date: Sat, 7 Nov 2020 01:08:41 +0100 Subject: [PATCH] Implement ranges::elements_view (#1406) Co-authored-by: S. B. Tam Co-authored-by: Casey Carter Co-authored-by: Stephan T. Lavavej --- stl/inc/ranges | 531 +++++++++++++++--- tests/std/test.lst | 1 + .../P0896R4_ranges_range_machinery/test.cpp | 3 + .../std/tests/P0896R4_views_elements/env.lst | 4 + .../std/tests/P0896R4_views_elements/test.cpp | 417 ++++++++++++++ 5 files changed, 878 insertions(+), 78 deletions(-) create mode 100644 tests/std/tests/P0896R4_views_elements/env.lst create mode 100644 tests/std/tests/P0896R4_views_elements/test.cpp diff --git a/stl/inc/ranges b/stl/inc/ranges index 8991d606b..e3e79f1a0 100644 --- a/stl/inc/ranges +++ b/stl/inc/ranges @@ -2336,6 +2336,116 @@ namespace ranges { inline constexpr _Drop_while_fn drop_while; } // namespace views + // CLASS TEMPLATE ranges::common_view + // clang-format off + template + requires (!common_range<_Vw> && copyable>) + class common_view : public view_interface> { + // clang-format on + private: + /* [[no_unique_address]] */ _Vw _Base{}; + + public: + common_view() = default; + constexpr explicit common_view(_Vw _Base_) noexcept(is_nothrow_move_constructible_v<_Vw>) // strengthened + : _Base(_STD move(_Base_)) {} + // converting constructor template omitted per LWG-3405 + + _NODISCARD constexpr _Vw base() const& noexcept( + is_nothrow_copy_constructible_v<_Vw>) /* strengthened */ requires copy_constructible<_Vw> { + return _Base; + } + _NODISCARD constexpr _Vw base() && noexcept(is_nothrow_move_constructible_v<_Vw>) /* strengthened */ { + return _STD move(_Base); + } + + _NODISCARD constexpr auto begin() noexcept( + noexcept(_RANGES begin(_Base)) && is_nothrow_move_constructible_v>) /* strengthened */ { + if constexpr (random_access_range<_Vw> && sized_range<_Vw>) { + return _RANGES begin(_Base); + } else { + return common_iterator, sentinel_t<_Vw>>{_RANGES begin(_Base)}; + } + } + + _NODISCARD constexpr auto begin() const noexcept( + noexcept(_RANGES begin(_Base)) + && is_nothrow_move_constructible_v>) /* strengthened */ requires range { + if constexpr (random_access_range && sized_range) { + return _RANGES begin(_Base); + } else { + return common_iterator, sentinel_t>{_RANGES begin(_Base)}; + } + } + + _NODISCARD constexpr auto end() { + if constexpr (random_access_range<_Vw> && sized_range<_Vw>) { + return _RANGES begin(_Base) + _RANGES size(_Base); + } else { + return common_iterator, sentinel_t<_Vw>>{_RANGES end(_Base)}; + } + } + + _NODISCARD constexpr auto end() const requires range { + if constexpr (random_access_range && sized_range) { + return _RANGES begin(_Base) + _RANGES size(_Base); + } else { + return common_iterator, sentinel_t>{_RANGES end(_Base)}; + } + } + + _NODISCARD constexpr auto size() noexcept( + noexcept(_RANGES size(_Base))) /* strengthened */ requires sized_range<_Vw> { + return _RANGES size(_Base); + } + _NODISCARD constexpr auto size() const + noexcept(noexcept(_RANGES size(_Base))) /* strengthened */ requires sized_range { + return _RANGES size(_Base); + } + }; + + template + common_view(_Rng &&) -> common_view>; + + namespace views { + // VARIABLE views::common + class _Common_fn : public _Pipe::_Base<_Common_fn> { + private: + enum class _St { _None, _All, _Common }; + + template + _NODISCARD static _CONSTEVAL _Choice_t<_St> _Choose() noexcept { + if constexpr (common_range<_Rng>) { + return {_St::_All, noexcept(views::all(_STD declval<_Rng>()))}; + } else if constexpr (copyable>) { + return {_St::_Common, noexcept(common_view{_STD declval<_Rng>()})}; + } else { + return {_St::_None}; + } + } + + template + static constexpr _Choice_t<_St> _Choice = _Choose<_Rng>(); + + public: + // clang-format off + template + requires (_Choice<_Rng>._Strategy != _St::_None) + _NODISCARD constexpr auto operator()(_Rng&& _Range) const noexcept(_Choice<_Rng>._No_throw) { + // clang-format on + if constexpr (_Choice<_Rng>._Strategy == _St::_All) { + return views::all(_STD forward<_Rng>(_Range)); + } else if constexpr (_Choice<_Rng>._Strategy == _St::_Common) { + return common_view{_STD forward<_Rng>(_Range)}; + } else { + static_assert(_Always_false<_Rng>, "Should be unreachable"); + } + } + }; + + inline constexpr _Common_fn common; + } // namespace views + // CLASS TEMPLATE ranges::reverse_view // clang-format off template @@ -2481,114 +2591,379 @@ namespace ranges { inline constexpr _Reverse_fn reverse; } // namespace views - // CLASS TEMPLATE ranges::common_view + // CLASS TEMPLATE ranges::elements_view + template + concept _Has_tuple_element = requires(_Tuple __t) { + typename tuple_size<_Tuple>::type; + requires _Index < tuple_size_v<_Tuple>; + typename tuple_element_t<_Index, _Tuple>; + // clang-format off + { _STD get<_Index>(__t) } -> convertible_to&>; + // clang-format on + }; + // clang-format off - template - requires (!common_range<_Vw> && copyable>) - class common_view : public view_interface> { + template + requires view<_Vw> && _Has_tuple_element, _Index> + && _Has_tuple_element>, _Index> + class elements_view : public view_interface> { // clang-format on private: - /* [[no_unique_address]] */ _Vw _Base{}; + /* [[no_unique_address]] */ _Vw _Range{}; + + template + class _Sentinel; + + template // TRANSITION, LWG-3289 + struct _Category_base {}; + + // clang-format off + template <_Has_member_iterator_category _Traits> + struct _Category_base<_Traits> { + // clang-format on + using iterator_category = typename _Traits::iterator_category; + }; + + template + class _Iterator : public _Category_base>> { + private: + template + friend class _Iterator; + template + friend class _Sentinel; + + using _Base = _Maybe_const<_Const, _Vw>; + + iterator_t<_Base> _Current{}; + + public: + using iterator_concept = conditional_t, random_access_iterator_tag, + conditional_t, bidirectional_iterator_tag, + conditional_t, forward_iterator_tag, input_iterator_tag>>>; + using value_type = remove_cvref_t>>; + using difference_type = range_difference_t<_Base>; + + _Iterator() = default; + + constexpr explicit _Iterator(iterator_t<_Base> _Current_) noexcept( + is_nothrow_move_constructible_v>) // strengthened + : _Current{_STD move(_Current_)} {} + + // clang-format off + constexpr _Iterator(_Iterator _It) noexcept( + is_nothrow_constructible_v, iterator_t<_Vw>>) // strengthened + requires _Const && convertible_to, iterator_t<_Base>> + : _Current{_STD move(_It._Current)} {} + // clang-format on + + _NODISCARD constexpr iterator_t<_Base> base() const& noexcept( + is_nothrow_copy_constructible_v>) /* strengthened */ + requires copyable> { + return _Current; + } + + _NODISCARD constexpr iterator_t<_Base> base() && noexcept( + is_nothrow_move_constructible_v>) /* strengthened */ { + return _STD move(_Current); + } + + _NODISCARD constexpr decltype(auto) operator*() const + noexcept(noexcept(_STD get<_Index>(*_Current))) /* strengthened */ { + return _STD get<_Index>(*_Current); + } + + constexpr _Iterator& operator++() noexcept(noexcept(++_Current)) /* strengthened */ { + ++_Current; + return *this; + } + + constexpr void operator++(int) noexcept(noexcept(++_Current)) /* strengthened */ { + // Constraint removed per LWG-3492 + ++_Current; + } + + constexpr _Iterator operator++(int) noexcept( + noexcept(++_Current) && is_nothrow_copy_constructible_v>) /* strengthened */ + requires forward_range<_Base> { + auto _Tmp = *this; + ++_Current; + return _Tmp; + } + + constexpr _Iterator& operator--() noexcept(noexcept(--_Current)) /* strengthened */ + requires bidirectional_range<_Base> { + --_Current; + return *this; + } + + constexpr _Iterator operator--(int) noexcept( + noexcept(--_Current) && is_nothrow_copy_constructible_v>) /* strengthened */ + requires bidirectional_range<_Base> { + auto _Tmp = *this; + --_Current; + return _Tmp; + } + + constexpr void _Verify_offset(const difference_type _Off) const requires random_access_range<_Base> { +#if _ITERATOR_DEBUG_LEVEL != 0 + (void) _Off; +#else // ^^^ _ITERATOR_DEBUG_LEVEL == 0 / _ITERATOR_DEBUG_LEVEL != 0 vvv + if constexpr (_Offset_verifiable_v>) { + _Current._Verify_offset(_Off); + } +#endif // _ITERATOR_DEBUG_LEVEL == 0 + } + + constexpr _Iterator& operator+=(const difference_type _Off) noexcept( + noexcept(_Current += _Off)) /* strengthened */ requires random_access_range<_Base> { +#if _ITERATOR_DEBUG_LEVEL != 0 + _Verify_offset(_Off); +#endif // _ITERATOR_DEBUG_LEVEL != 0 + _Current += _Off; + return *this; + } + constexpr _Iterator& operator-=(const difference_type _Off) noexcept( + noexcept(_Current -= _Off)) /* strengthened */ requires random_access_range<_Base> { +#if _ITERATOR_DEBUG_LEVEL != 0 + _Verify_offset(-_Off); +#endif // _ITERATOR_DEBUG_LEVEL != 0 + _Current -= _Off; + return *this; + } + + _NODISCARD constexpr decltype(auto) operator[](const difference_type _Idx) const + noexcept(noexcept(_STD get<_Index>(*(_Current + _Idx)))) /* strengthened */ + requires random_access_range<_Base> { +#if _ITERATOR_DEBUG_LEVEL != 0 + _Verify_offset(_Idx); +#endif // _ITERATOR_DEBUG_LEVEL != 0 + return _STD get<_Index>(*(_Current + _Idx)); + } + + _NODISCARD friend constexpr bool operator==(const _Iterator& _Left, const _Iterator& _Right) noexcept( + noexcept(_Left._Current == _Right._Current)) /* strengthened */ + requires equality_comparable> { + return _Left._Current == _Right._Current; + } + + _NODISCARD friend constexpr bool operator<(const _Iterator& _Left, const _Iterator& _Right) noexcept( + noexcept(_Left._Current < _Right._Current)) /* strengthened */ requires random_access_range<_Base> { + return _Left._Current < _Right._Current; + } + _NODISCARD friend constexpr bool operator>(const _Iterator& _Left, const _Iterator& _Right) noexcept( + noexcept(_Left._Current < _Right._Current)) /* strengthened */ requires random_access_range<_Base> { + return _Right < _Left; + } + _NODISCARD friend constexpr bool operator<=(const _Iterator& _Left, const _Iterator& _Right) noexcept( + noexcept(_Left._Current < _Right._Current)) /* strengthened */ requires random_access_range<_Base> { + return !(_Right < _Left); + } + _NODISCARD friend constexpr bool operator>=(const _Iterator& _Left, const _Iterator& _Right) noexcept( + noexcept(_Left._Current < _Right._Current)) /* strengthened */ requires random_access_range<_Base> { + return !(_Left < _Right); + } + + // clang-format off + _NODISCARD friend constexpr auto operator<=>(const _Iterator& _Left, const _Iterator& _Right) noexcept( + noexcept(_Left._Current <=> _Right._Current)) /* strengthened */ + requires random_access_range<_Base> && three_way_comparable> { + // clang-format on + return _Left._Current <=> _Right._Current; + } + + _NODISCARD friend constexpr _Iterator operator+(const _Iterator& _It, const difference_type _Off) noexcept( + noexcept(_STD declval&>() += _Off) + && is_nothrow_copy_constructible_v>) /* strengthened */ + requires random_access_range<_Base> { +#if _ITERATOR_DEBUG_LEVEL != 0 + _It._Verify_offset(_Off); +#endif // _ITERATOR_DEBUG_LEVEL != 0 + auto _Copy = _It; + _Copy._Current += _Off; + return _Copy; + } + + _NODISCARD friend constexpr _Iterator operator+(const difference_type _Off, const _Iterator& _It) noexcept( + noexcept(_STD declval&>() += _Off) + && is_nothrow_copy_constructible_v>) /* strengthened */ + requires random_access_range<_Base> { +#if _ITERATOR_DEBUG_LEVEL != 0 + _It._Verify_offset(_Off); +#endif // _ITERATOR_DEBUG_LEVEL != 0 + auto _Copy = _It; + _Copy._Current += _Off; + return _Copy; + } + + _NODISCARD friend constexpr _Iterator operator-(const _Iterator& _It, const difference_type _Off) noexcept( + noexcept(_STD declval&>() -= _Off) + && is_nothrow_copy_constructible_v>) /* strengthened */ + requires random_access_range<_Base> { +#if _ITERATOR_DEBUG_LEVEL != 0 + _It._Verify_offset(-_Off); +#endif // _ITERATOR_DEBUG_LEVEL != 0 + auto _Copy = _It; + _Copy._Current -= _Off; + return _Copy; + } + + _NODISCARD friend constexpr difference_type operator-(const _Iterator& _Left, + const _Iterator& _Right) noexcept(noexcept(_Left._Current - _Right._Current)) /* strengthened */ + requires sized_sentinel_for, iterator_t<_Base>> { // Per LWG-3483 + return _Left._Current - _Right._Current; + } + }; + + template + class _Sentinel { + private: + template + friend class _Sentinel; + + using _Base = _Maybe_const<_Const, _Vw>; + + sentinel_t<_Base> _Last{}; + + template + _NODISCARD static constexpr const iterator_t<_Maybe_const<_OtherConst, _Vw>>& _Get_current( + const _Iterator<_OtherConst>& _It) noexcept { + return _It._Current; + } + + public: + _Sentinel() = default; + constexpr explicit _Sentinel(sentinel_t<_Base> _Last_) noexcept( + is_nothrow_move_constructible_v>) // strengthened + : _Last(_STD move(_Last_)) {} + + // clang-format off + constexpr _Sentinel(_Sentinel _Se) + noexcept(is_nothrow_constructible_v, sentinel_t<_Vw>>) // strengthened + requires _Const && convertible_to, sentinel_t<_Base>> + : _Last(_STD move(_Se._Last)) {} + // clang-format on + + _NODISCARD constexpr sentinel_t<_Base> base() const + noexcept(is_nothrow_copy_constructible_v>) /* strengthened */ { + return _Last; + } + + // clang-format off + template // Per resolution of LWG-3406 + requires sentinel_for, iterator_t<_Maybe_const<_OtherConst, _Vw>>> + _NODISCARD friend constexpr bool operator==(const _Iterator<_OtherConst>& _Left, + const _Sentinel& _Right) noexcept(noexcept(_Get_current(_Left) == _Right._Last)) /* strengthened */ { + // clang-format on + return _Get_current(_Left) == _Right._Last; + } + + // clang-format off + template // Per resolution of LWG-3406 + requires sized_sentinel_for, iterator_t<_Maybe_const<_OtherConst, _Vw>>> + _NODISCARD friend constexpr range_difference_t<_Maybe_const<_OtherConst, _Vw>> operator-( + const _Iterator<_OtherConst>& _Left, const _Sentinel& _Right) noexcept( + noexcept(_Get_current(_Left) - _Right._Last)) /* strengthened */ { + // clang-format on + return _Get_current(_Left) - _Right._Last; + } + + // clang-format off + template // Per resolution of LWG-3406 + requires sized_sentinel_for, iterator_t<_Maybe_const<_OtherConst, _Vw>>> + _NODISCARD friend constexpr range_difference_t<_Maybe_const<_OtherConst, _Vw>> operator-( + const _Sentinel& _Left, const _Iterator<_OtherConst>& _Right) noexcept( + noexcept(_Left._Last - _Get_current(_Right))) /* strengthened */ { + // clang-format on + return _Left._Last - _Get_current(_Right); + } + }; public: - common_view() = default; - constexpr explicit common_view(_Vw _Base_) noexcept(is_nothrow_move_constructible_v<_Vw>) // strengthened - : _Base(_STD move(_Base_)) {} - // converting constructor template omitted per LWG-3405 + elements_view() = default; - _NODISCARD constexpr _Vw base() const& noexcept( - is_nothrow_copy_constructible_v<_Vw>) /* strengthened */ requires copy_constructible<_Vw> { - return _Base; + constexpr explicit elements_view(_Vw _Range_) noexcept(is_nothrow_move_constructible_v<_Vw>) // strengthened + : _Range(_STD move(_Range_)) {} + + _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(_Base); + return _STD move(_Range); } - _NODISCARD constexpr auto begin() noexcept( - noexcept(_RANGES begin(_Base)) && is_nothrow_move_constructible_v>) /* strengthened */ { - if constexpr (random_access_range<_Vw> && sized_range<_Vw>) { - return _RANGES begin(_Base); + // clang-format off + _NODISCARD constexpr _Iterator begin() noexcept( + noexcept(_RANGES begin(_Range)) && is_nothrow_move_constructible_v>) /* strengthened */ + requires (!_Simple_view<_Vw>) { + // clang-format on + return _Iterator{_RANGES begin(_Range)}; + } + + _NODISCARD constexpr _Iterator begin() const + noexcept(noexcept(_RANGES begin(_Range)) + && is_nothrow_move_constructible_v>) /* strengthened */ + requires range { // Per resolution of LWG-3406 + return _Iterator{_RANGES begin(_Range)}; + } + + // clang-format off + _NODISCARD constexpr auto end() noexcept(noexcept( + _RANGES end(_Range)) && is_nothrow_move_constructible_v>) /* strengthened */ + requires (!_Simple_view<_Vw>) { // Per resolution of LWG-3406 + // clang-format on + if constexpr (common_range<_Vw>) { + return _Iterator{_RANGES end(_Range)}; } else { - return common_iterator, sentinel_t<_Vw>>{_RANGES begin(_Base)}; + return _Sentinel{_RANGES end(_Range)}; } } - _NODISCARD constexpr auto begin() const noexcept( - noexcept(_RANGES begin(_Base)) - && is_nothrow_move_constructible_v>) /* strengthened */ requires range { - if constexpr (random_access_range && sized_range) { - return _RANGES begin(_Base); + // clang-format off + _NODISCARD constexpr auto end() const noexcept(noexcept( + _RANGES end(_Range)) && is_nothrow_move_constructible_v>) /* strengthened */ + requires range { + // clang-format on + if constexpr (common_range) { + return _Iterator{_RANGES end(_Range)}; } else { - return common_iterator, sentinel_t>{_RANGES begin(_Base)}; + return _Sentinel{_RANGES end(_Range)}; } } - _NODISCARD constexpr auto end() { - if constexpr (random_access_range<_Vw> && sized_range<_Vw>) { - return _RANGES begin(_Base) + _RANGES size(_Base); - } else { - return common_iterator, sentinel_t<_Vw>>{_RANGES end(_Base)}; - } + _NODISCARD constexpr auto size() noexcept(noexcept(_RANGES size(_Range))) /* strengthened */ + requires sized_range<_Vw> { + return _RANGES size(_Range); } - - _NODISCARD constexpr auto end() const requires range { - if constexpr (random_access_range && sized_range) { - return _RANGES begin(_Base) + _RANGES size(_Base); - } else { - return common_iterator, sentinel_t>{_RANGES end(_Base)}; - } - } - - _NODISCARD constexpr auto size() noexcept( - noexcept(_RANGES size(_Base))) /* strengthened */ requires sized_range<_Vw> { - return _RANGES size(_Base); - } - _NODISCARD constexpr auto size() const - noexcept(noexcept(_RANGES size(_Base))) /* strengthened */ requires sized_range { - return _RANGES size(_Base); + _NODISCARD constexpr auto size() const noexcept(noexcept(_RANGES size(_Range))) /* strengthened */ + requires sized_range { + return _RANGES size(_Range); } }; template - common_view(_Rng &&) -> common_view>; + using keys_view = elements_view, 0>; + template + using values_view = elements_view, 1>; namespace views { - // VARIABLE views::common - class _Common_fn : public _Pipe::_Base<_Common_fn> { - private: - enum class _St { _None, _All, _Common }; - - template - _NODISCARD static _CONSTEVAL _Choice_t<_St> _Choose() noexcept { - if constexpr (common_range<_Rng>) { - return {_St::_All, noexcept(views::all(_STD declval<_Rng>()))}; - } else if constexpr (copyable>) { - return {_St::_Common, noexcept(common_view{_STD declval<_Rng>()})}; - } else { - return {_St::_None}; - } - } - - template - static constexpr _Choice_t<_St> _Choice = _Choose<_Rng>(); - + // VARIABLE views::elements + template + class _Elements_fn : public _Pipe::_Base<_Elements_fn<_Index>> { public: - // clang-format off template - requires (_Choice<_Rng>._Strategy != _St::_None) - _NODISCARD constexpr auto operator()(_Rng&& _Range) const noexcept(_Choice<_Rng>._No_throw) { - // clang-format on - if constexpr (_Choice<_Rng>._Strategy == _St::_All) { - return views::all(_STD forward<_Rng>(_Range)); - } else if constexpr (_Choice<_Rng>._Strategy == _St::_Common) { - return common_view{_STD forward<_Rng>(_Range)}; - } else { - static_assert(_Always_false<_Rng>, "Should be unreachable"); - } + _NODISCARD constexpr auto operator()(_Rng&& _Range) const noexcept( + noexcept(elements_view, _Index>{_STD forward<_Rng>(_Range)})) requires requires { + elements_view, _Index>{static_cast<_Rng&&>(_Range)}; } + { return elements_view, _Index>{_STD forward<_Rng>(_Range)}; } }; - inline constexpr _Common_fn common; + template + inline constexpr _Elements_fn<_Index> elements; + inline constexpr auto keys = elements<0>; + inline constexpr auto values = elements<1>; } // namespace views } // namespace ranges diff --git a/tests/std/test.lst b/tests/std/test.lst index 28c93c01d..3ab17a6d8 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -351,6 +351,7 @@ tests\P0896R4_views_counted_death tests\P0896R4_views_drop tests\P0896R4_views_drop_while tests\P0896R4_views_drop_while_death +tests\P0896R4_views_elements 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 b6207447a..bb57295ec 100644 --- a/tests/std/tests/P0896R4_ranges_range_machinery/test.cpp +++ b/tests/std/tests/P0896R4_ranges_range_machinery/test.cpp @@ -102,12 +102,15 @@ STATIC_ASSERT(test_cpo(ranges::views::common)); STATIC_ASSERT(test_cpo(ranges::views::counted)); STATIC_ASSERT(test_cpo(ranges::views::drop)); STATIC_ASSERT(test_cpo(ranges::views::drop_while)); +STATIC_ASSERT(test_cpo(ranges::views::elements<42>)); STATIC_ASSERT(test_cpo(ranges::views::filter)); +STATIC_ASSERT(test_cpo(ranges::views::keys)); STATIC_ASSERT(test_cpo(ranges::views::reverse)); STATIC_ASSERT(test_cpo(ranges::views::single)); STATIC_ASSERT(test_cpo(ranges::views::take)); STATIC_ASSERT(test_cpo(ranges::views::take_while)); STATIC_ASSERT(test_cpo(ranges::views::transform)); +STATIC_ASSERT(test_cpo(ranges::views::values)); void test_cpo_ambiguity() { using namespace std::ranges; diff --git a/tests/std/tests/P0896R4_views_elements/env.lst b/tests/std/tests/P0896R4_views_elements/env.lst new file mode 100644 index 000000000..62a240244 --- /dev/null +++ b/tests/std/tests/P0896R4_views_elements/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\strict_concepts_matrix.lst diff --git a/tests/std/tests/P0896R4_views_elements/test.cpp b/tests/std/tests/P0896R4_views_elements/test.cpp new file mode 100644 index 000000000..82c58fae6 --- /dev/null +++ b/tests/std/tests/P0896R4_views_elements/test.cpp @@ -0,0 +1,417 @@ +// 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; +using P = pair; + +// Validate views::keys and views::values +STATIC_ASSERT(same_as, remove_const_t)>>); +STATIC_ASSERT(same_as, remove_const_t)>>); + +constexpr auto pipeline = views::elements<0> | views::all; + +template > +using pipeline_t = ranges::elements_view; + +template +concept CanViewElements = requires(Rng&& r) { + views::elements<0>(static_cast(r)); +}; + +constexpr P some_pairs[] = {{0, -1}, {1, -2}, {2, -3}, {3, -4}, {4, -5}, {5, -6}, {6, -7}, {7, -8}}; +constexpr int expected_keys[] = {0, 1, 2, 3, 4, 5, 6, 7}; +constexpr int expected_values[] = {-1, -2, -3, -4, -5, -6, -7, -8}; + +template +constexpr bool test_one(Rng&& rng) { + using ranges::elements_view, ranges::bidirectional_range, ranges::common_range, ranges::contiguous_range, + ranges::enable_borrowed_range, ranges::forward_range, ranges::input_range, ranges::iterator_t, ranges::prev, + ranges::random_access_range, ranges::range, ranges::range_reference_t, ranges::sentinel_t; + + using V = views::all_t; + using R = elements_view; + STATIC_ASSERT(ranges::view); + STATIC_ASSERT(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); + + // ... with lvalue argument + STATIC_ASSERT(CanViewElements); + constexpr bool is_view = ranges::view>; + if constexpr (CanViewElements) { // Validate lvalue + constexpr bool is_noexcept = !is_view || is_nothrow_copy_constructible_v; + + STATIC_ASSERT(same_as(rng)), R>); + STATIC_ASSERT(noexcept(views::elements<0>(rng)) == is_noexcept); + + STATIC_ASSERT(same_as), R>); + STATIC_ASSERT(noexcept(rng | views::elements<0>) == is_noexcept); + + STATIC_ASSERT(same_as>); + STATIC_ASSERT(noexcept(rng | pipeline) == is_noexcept); + } + + // ... with const lvalue argument + STATIC_ASSERT(CanViewElements&> == (!is_view || copyable) ); + if constexpr (is_view && copyable) { + constexpr bool is_noexcept = is_nothrow_copy_constructible_v; + + STATIC_ASSERT(same_as(as_const(rng))), R>); + STATIC_ASSERT(noexcept(views::elements<0>(as_const(rng))) == is_noexcept); + + STATIC_ASSERT(same_as), R>); + STATIC_ASSERT(noexcept(as_const(rng) | views::elements<0>) == is_noexcept); + + STATIC_ASSERT(same_as&>>); + STATIC_ASSERT(noexcept(as_const(rng) | pipeline) == is_noexcept); + } else if constexpr (!is_view) { + using RC = elements_view&>, 0>; + constexpr bool is_noexcept = is_nothrow_constructible_v&>; + + STATIC_ASSERT(same_as(as_const(rng))), RC>); + STATIC_ASSERT(noexcept(views::elements<0>(as_const(rng))) == is_noexcept); + + STATIC_ASSERT(same_as), RC>); + STATIC_ASSERT(noexcept(as_const(rng) | views::elements<0>) == is_noexcept); + + STATIC_ASSERT(same_as&>>); + STATIC_ASSERT(noexcept(as_const(rng) | pipeline) == is_noexcept); + } + + // ... with rvalue argument + STATIC_ASSERT(CanViewElements> == is_view || enable_borrowed_range>); + if constexpr (is_view) { + constexpr bool is_noexcept = is_nothrow_move_constructible_v; + STATIC_ASSERT(same_as(move(rng))), R>); + STATIC_ASSERT(noexcept(views::elements<0>(move(rng))) == is_noexcept); + + STATIC_ASSERT(same_as), R>); + STATIC_ASSERT(noexcept(move(rng) | views::elements<0>) == 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 = elements_view; + constexpr bool is_noexcept = noexcept(S{move(rng)}); + + STATIC_ASSERT(same_as(move(rng))), RS>); + STATIC_ASSERT(noexcept(views::elements<0>(move(rng))) == is_noexcept); + + STATIC_ASSERT(same_as), RS>); + STATIC_ASSERT(noexcept(move(rng) | views::elements<0>) == is_noexcept); + + STATIC_ASSERT(same_as>>); + STATIC_ASSERT(noexcept(move(rng) | pipeline) == is_noexcept); + } + + // ... with const rvalue argument + STATIC_ASSERT(CanViewElements> == (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(move(as_const(rng)))), R>); + STATIC_ASSERT(noexcept(views::elements<0>(move(as_const(rng)))) == is_noexcept); + + STATIC_ASSERT(same_as), R>); + STATIC_ASSERT(noexcept(move(as_const(rng)) | views::elements<0>) == 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 = elements_view; + constexpr bool is_noexcept = noexcept(S{move(as_const(rng))}); + + STATIC_ASSERT(same_as(move(as_const(rng)))), RS>); + STATIC_ASSERT(noexcept(views::elements<0>(move(as_const(rng)))) == is_noexcept); + + STATIC_ASSERT(same_as), RS>); + STATIC_ASSERT(noexcept(move(as_const(rng)) | views::elements<0>) == is_noexcept); + + STATIC_ASSERT(same_as>>); + STATIC_ASSERT(noexcept(move(as_const(rng)) | pipeline) == is_noexcept); + } + + // Validate concepts are properly modeled + R r{forward(rng)}; + STATIC_ASSERT(ranges::view); + STATIC_ASSERT(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); + + // Validate elements_view::size + STATIC_ASSERT(CanMemberSize == CanSize); + STATIC_ASSERT(CanSize == CanMemberSize); + STATIC_ASSERT(CanMemberSize == CanSize); + STATIC_ASSERT(CanSize == CanMemberSize); + if constexpr (CanMemberSize) { + assert(r.size() == static_cast(ranges::size(expected_keys))); + if constexpr (CanMemberSize) { + assert(as_const(r).size() == static_cast(ranges::size(expected_keys))); + } + } + + const bool is_empty = ranges::empty(expected_keys); + + // 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(CanMemberEmpty == forward_range); + STATIC_ASSERT(CanBool == CanEmpty); + if constexpr (CanMemberEmpty) { + assert(as_const(r).empty() == is_empty); + assert(static_cast(as_const(r)) == !is_empty); + } else { + STATIC_ASSERT(CanEmpty == CanSize); + if constexpr (CanEmpty) { + assert(ranges::empty(as_const(r)) == is_empty); + assert(static_cast(as_const(r)) == !is_empty); + } + } + + // Validate content + assert(ranges::equal(r, expected_keys)); + + // Validate keys_view and values_view + STATIC_ASSERT(same_as, R>); + STATIC_ASSERT(same_as, elements_view>); + if constexpr (forward_range && is_lvalue_reference_v) { + assert(ranges::equal(ranges::values_view{rng}, expected_values)); + } + + // Validate elements_view::begin + STATIC_ASSERT(CanMemberBegin); + STATIC_ASSERT(CanBegin == (range) ); + if (forward_range) { // intentionally not if constexpr + const same_as> auto i = r.begin(); + if (!is_empty) { + assert(*i == *begin(expected_keys)); + } + + if constexpr (copyable) { + auto r2 = r; + const same_as> auto i2 = r2.begin(); + if (!is_empty) { + assert(*i2 == *i); + } + } + + if constexpr (CanBegin) { + const same_as> auto i3 = as_const(r).begin(); + if (!is_empty) { + assert(*i3 == *i); + } + } + } + + // Validate elements_view::end + STATIC_ASSERT(CanMemberEnd); + STATIC_ASSERT(CanEnd == range); + if (!is_empty) { + same_as> auto i = r.end(); + static_assert(common_range == common_range); + if constexpr (bidirectional_range && common_range) { +#if defined(__clang__) || defined(__EDG__) // TRANSITION, VSO-1243263 + assert(*prev(i) == *prev(end(expected_keys))); +#else // ^^^ no workaround / workaround vvv + assert(*ranges::prev(i) == *ranges::prev(end(expected_keys))); +#endif // ^^^ workaround ^^^ + } + + if constexpr (CanEnd) { + same_as> auto i2 = as_const(r).end(); + static_assert(common_range == common_range); + if constexpr (bidirectional_range && common_range) { +#if defined(__clang__) || defined(__EDG__) // TRANSITION, VSO-1243263 + assert(*prev(i2) == *prev(end(expected_keys))); +#else // ^^^ no workaround / workaround vvv + assert(*ranges::prev(i2) == *ranges::prev(end(expected_keys))); +#endif // ^^^ workaround ^^^ + } + } + } + + // Validate view_interface::data + STATIC_ASSERT(!CanData); + STATIC_ASSERT(!CanData); + + // Validate view_interface::front and back + if (!is_empty) { + STATIC_ASSERT(CanMemberFront == forward_range); + if constexpr (CanMemberFront) { + assert(r.front() == *begin(expected_keys)); + } + + STATIC_ASSERT(CanMemberBack == (bidirectional_range && common_range) ); + if constexpr (CanMemberBack) { +#if defined(__clang__) || defined(__EDG__) // TRANSITION, VSO-1243263 + assert(r.back() == *prev(end(expected_keys))); +#else // ^^^ no workaround / workaround vvv + assert(r.back() == *ranges::prev(end(expected_keys))); +#endif // ^^^ workaround ^^^ + } + + STATIC_ASSERT(CanMemberFront == (forward_range) ); + if constexpr (CanMemberFront) { + assert(as_const(r).front() == *begin(expected_keys)); + } + + STATIC_ASSERT(CanMemberBack == (bidirectional_range && common_range) ); + if constexpr (CanMemberBack) { +#if defined(__clang__) || defined(__EDG__) // TRANSITION, VSO-1243263 + assert(as_const(r).back() == *prev(end(expected_keys))); +#else // ^^^ no workaround / workaround vvv + assert(as_const(r).back() == *ranges::prev(end(expected_keys))); +#endif // ^^^ workaround ^^^ + } + } + + // Validate view_interface::operator[] + if (!is_empty) { + STATIC_ASSERT(CanIndex == random_access_range); + if constexpr (CanIndex) { + assert(r[0] == *r.begin()); + } + + STATIC_ASSERT(CanIndex == random_access_range); + if constexpr (CanIndex) { + assert(as_const(r)[0] == *as_const(r).begin()); + } + } + + // Validate elements_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() == pair{0, -1})); // NB: depends on the test data + if constexpr (bidirectional_range && common_range) { +#if defined(__clang__) || defined(__EDG__) // TRANSITION, VSO-1243263 + assert((*prev(b1.end()) == pair{7, -8})); // NB: depends on the test data +#else // ^^^ no workaround / workaround vvv + assert((*ranges::prev(b1.end()) == pair{7, -8})); // NB: depends on the test data +#endif // ^^^ workaround ^^^ + } + } + } + + // Validate elements_view::base() && (NB: do this last since it leaves r moved-from) + 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() == pair{0, -1})); // NB: depends on the test data + if constexpr (bidirectional_range && common_range) { +#if defined(__clang__) || defined(__EDG__) // TRANSITION, VSO-1243263 + assert((*prev(b2.end()) == pair{7, -8})); // NB: depends on the test data +#else // ^^^ no workaround / workaround vvv + assert((*ranges::prev(b2.end()) == pair{7, -8})); // NB: depends on the test data +#endif // ^^^ workaround ^^^ + } + } + } + return true; +} + +struct instantiator { + template + static constexpr void call() { + R r{some_pairs}; + test_one(r); + } +}; + +template +using test_range = + test::range}, + IsCommon, test::CanCompare{derived_from || IsCommon == test::Common::yes}, + test::ProxyRef::no>; + +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, commonality, size, and differencing, but cannot handle proxies. + using test::Common, test::Sized; + + instantiator::call>(); + instantiator::call>(); + instantiator::call>(); + instantiator::call>(); + instantiator::call>(); + instantiator::call>(); + instantiator::call>(); + instantiator::call>(); + instantiator::call>(); + instantiator::call>(); + instantiator::call>(); + instantiator::call>(); + instantiator::call>(); + instantiator::call>(); + instantiator::call>(); + instantiator::call>(); + instantiator::call>(); + instantiator::call>(); + instantiator::call>(); + instantiator::call>(); +#endif // TEST_EVERYTHING +} + +int main() { + { // Validate copyable views + constexpr span s{some_pairs}; + STATIC_ASSERT(test_one(s)); + test_one(s); + } + + { // Validate non-views + STATIC_ASSERT(test_one(some_pairs)); + test_one(some_pairs); + + { + vector vec(ranges::begin(some_pairs), ranges::end(some_pairs)); + test_one(vec); + } + { + forward_list lst(ranges::begin(some_pairs), ranges::end(some_pairs)); + test_one(lst); + } + + STATIC_ASSERT((instantiation_test(), true)); + instantiation_test(); + } + + { // Validate a non-view borrowed range + constexpr span s{some_pairs}; + STATIC_ASSERT(test_one(s)); + test_one(s); + } +}