diff --git a/stl/inc/ranges b/stl/inc/ranges index 776e1310ef..10f7c9d04b 100644 --- a/stl/inc/ranges +++ b/stl/inc/ranges @@ -2059,6 +2059,116 @@ namespace ranges { inline constexpr _Reverse_fn reverse; } // 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 } // namespace ranges namespace views = ranges::views; diff --git a/tests/std/test.lst b/tests/std/test.lst index 2ac454e53c..9b439a1c33 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -339,6 +339,7 @@ tests\P0896R4_ranges_test_machinery tests\P0896R4_ranges_to_address tests\P0896R4_stream_iterators tests\P0896R4_views_all +tests\P0896R4_views_common tests\P0896R4_views_drop tests\P0896R4_views_empty tests\P0896R4_views_filter diff --git a/tests/std/tests/P0896R4_ranges_range_machinery/test.cpp b/tests/std/tests/P0896R4_ranges_range_machinery/test.cpp index 1605cb5ebb..0ff01a23ac 100644 --- a/tests/std/tests/P0896R4_ranges_range_machinery/test.cpp +++ b/tests/std/tests/P0896R4_ranges_range_machinery/test.cpp @@ -90,6 +90,7 @@ STATIC_ASSERT(test_cpo(ranges::data)); STATIC_ASSERT(test_cpo(ranges::cdata)); STATIC_ASSERT(test_cpo(ranges::views::all)); +STATIC_ASSERT(test_cpo(ranges::views::common)); STATIC_ASSERT(test_cpo(ranges::views::drop)); STATIC_ASSERT(test_cpo(ranges::views::filter)); STATIC_ASSERT(test_cpo(ranges::views::reverse)); diff --git a/tests/std/tests/P0896R4_views_common/env.lst b/tests/std/tests/P0896R4_views_common/env.lst new file mode 100644 index 0000000000..62a2402447 --- /dev/null +++ b/tests/std/tests/P0896R4_views_common/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_common/test.cpp b/tests/std/tests/P0896R4_views_common/test.cpp new file mode 100644 index 0000000000..111c1eac49 --- /dev/null +++ b/tests/std/tests/P0896R4_views_common/test.cpp @@ -0,0 +1,389 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include + +#include +using namespace std; + +template +concept CanViewCommon = requires(Rng&& r) { + views::common(static_cast(r)); +}; + +template +concept CanViewAll = requires(Rng&& r) { + views::all(static_cast(r)); +}; + +// Test a silly precomposed range adaptor pipeline +constexpr auto pipeline = views::all | views::common; + +// Due to language limitations we cannot declare variables of non-literal type in a branch that is guarded by +// `!is_constant_evaluated()`. But we can call a non-constexpr function that declares those variables. +template +void non_literal_parts(R& r, E& expected) { + using ranges::iterator_t, ranges::begin, ranges::bidirectional_range, ranges::end, ranges::prev; + + const bool is_empty = ranges::empty(expected); + + const same_as> auto first = r.begin(); + if (!is_empty) { + assert(*first == *begin(expected)); + } + + if constexpr (copyable) { + auto r2 = r; + const same_as> auto first2 = r2.begin(); + if (!is_empty) { + assert(*first2 == *first); + } + } + + if constexpr (CanBegin) { + const same_as> auto first3 = as_const(r).begin(); + if (!is_empty) { + assert(*first3 == *first); + } + } + + const same_as> auto last = r.end(); + if constexpr (bidirectional_range) { + if (!is_empty) { + assert(*prev(last) == *prev(end(expected))); + } + } + + if constexpr (CanEnd) { + const same_as> auto last2 = as_const(r).end(); + if constexpr (bidirectional_range) { + if (!is_empty) { + assert(*prev(last2) == *prev(end(expected))); + } + } + } +} + +template +constexpr bool test_one(Rng&& rng, Expected&& expected) { + using ranges::common_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::size, ranges::sized_range, + ranges::range_size_t; + + constexpr bool is_view = ranges::view>; + using V = views::all_t; + constexpr bool is_common = common_range; + + // Validate range adaptor object + if constexpr (!is_common) { // range adaptor results in common_view + using R = common_view; + static_assert(ranges::view); + + // ...with lvalue argument + static_assert(CanViewCommon == (!is_view && copyable) ); + if constexpr (CanViewCommon) { + constexpr bool is_noexcept = !is_view || is_nothrow_copy_constructible_v; + + static_assert(same_as); + static_assert(noexcept(views::common(rng)) == is_noexcept); + + static_assert(same_as); + static_assert(noexcept(rng | views::common) == is_noexcept); + } + + // ... with const lvalue argument + static_assert(CanViewCommon&> == (!is_view || copyable) ); + if constexpr (is_view && copyable) { + constexpr bool is_noexcept = is_nothrow_copy_constructible_v; + + static_assert(same_as); + static_assert(noexcept(views::common(as_const(rng))) == is_noexcept); + + static_assert(same_as); + static_assert(noexcept(as_const(rng) | views::common) == is_noexcept); + + static_assert(same_as); + static_assert(noexcept(as_const(rng) | views::common | views::common | views::common) == is_noexcept); + + static_assert(same_as); + static_assert(noexcept(as_const(rng) | pipeline) == is_noexcept); + } else if constexpr (!is_view) { + using RC = common_view&>>; + constexpr bool is_noexcept = is_nothrow_constructible_v&>; + + static_assert(same_as); + static_assert(noexcept(views::common(as_const(rng))) == is_noexcept); + + static_assert(same_as); + static_assert(noexcept(as_const(rng) | views::common) == is_noexcept); + + static_assert(same_as); + static_assert(noexcept(as_const(rng) | views::common | views::common | views::common) == is_noexcept); + + static_assert(same_as); + static_assert(noexcept(as_const(rng) | pipeline) == is_noexcept); + } + + // ... with rvalue argument + static_assert(CanViewCommon> == is_view || enable_borrowed_range>); + if constexpr (is_view) { + constexpr bool is_noexcept = is_nothrow_move_constructible_v; + static_assert(same_as); + static_assert(noexcept(views::common(move(rng))) == is_noexcept); + + static_assert(same_as); + static_assert(noexcept(move(rng) | views::common) == is_noexcept); + + static_assert(same_as); + static_assert(noexcept(move(rng) | views::common | views::common | views::common) == 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 = common_view; + constexpr bool is_noexcept = noexcept(S{move(rng)}); + + static_assert(same_as); + static_assert(noexcept(views::common(move(rng))) == is_noexcept); + + static_assert(same_as); + static_assert(noexcept(move(rng) | views::common) == is_noexcept); + + static_assert(same_as); + static_assert(noexcept(move(rng) | views::common | views::common | views::common) == is_noexcept); + + static_assert(same_as); + static_assert(noexcept(move(rng) | pipeline) == is_noexcept); + } + + // ... with const rvalue argument + static_assert(CanViewCommon> == (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); + static_assert(noexcept(views::common(move(as_const(rng)))) == is_nothrow_copy_constructible_v); + + static_assert(same_as); + static_assert(noexcept(move(as_const(rng)) | views::common) == is_nothrow_copy_constructible_v); + + static_assert(same_as); + static_assert(noexcept(move(as_const(rng)) | views::common | views::common | views::common) == 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 = common_view; + constexpr bool is_noexcept = noexcept(S{move(as_const(rng))}); + + static_assert(same_as); + static_assert(noexcept(views::common(move(as_const(rng)))) == is_noexcept); + + static_assert(same_as); + static_assert(noexcept(move(as_const(rng)) | views::common) == is_noexcept); + + static_assert(same_as); + static_assert(noexcept(move(as_const(rng)) | views::common | views::common | views::common) == is_noexcept); + + static_assert(same_as); + static_assert(noexcept(move(as_const(rng)) | pipeline) == is_noexcept); + } + } else { // range adaptor results in views::all_t + // ...with lvalue argument + { + constexpr bool is_noexcept = !is_view || is_nothrow_copy_constructible_v; + + static_assert(same_as); + static_assert(noexcept(views::common(rng)) == is_noexcept); + + static_assert(same_as); + static_assert(noexcept(rng | views::common) == is_noexcept); + } + + // ... with const lvalue argument + if constexpr (is_view && copyable) { + constexpr bool is_noexcept = is_nothrow_copy_constructible_v; + + static_assert(same_as); + static_assert(noexcept(views::common(as_const(rng))) == is_noexcept); + + static_assert(same_as); + static_assert(noexcept(as_const(rng) | views::common) == is_noexcept); + } else if constexpr (!is_view) { + using RC = views::all_t&>; + constexpr bool is_noexcept = is_nothrow_constructible_v&>; + + static_assert(same_as); + static_assert(noexcept(views::common(as_const(rng))) == is_noexcept); + + static_assert(same_as); + static_assert(noexcept(as_const(rng) | views::common) == is_noexcept); + } + // ... with rvalue argument + static_assert(CanViewCommon> == is_view || enable_borrowed_range>); + if constexpr (is_view) { + constexpr bool is_noexcept = is_nothrow_move_constructible_v; + static_assert(same_as); + static_assert(noexcept(views::common(move(rng))) == is_noexcept); + + static_assert(same_as); + static_assert(noexcept(move(rng) | views::common) == is_noexcept); + } else if constexpr (enable_borrowed_range>) { + using S = decltype(ranges::subrange{move(rng)}); + constexpr bool is_noexcept = noexcept(S{move(rng)}); + + static_assert(same_as); + static_assert(noexcept(views::common(move(rng))) == is_noexcept); + + static_assert(same_as); + static_assert(noexcept(move(rng) | views::common) == is_noexcept); + } + + // ... with const rvalue argument + static_assert(CanViewCommon> == (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); + static_assert(noexcept(views::common(move(as_const(rng)))) == is_noexcept); + + static_assert(same_as); + static_assert(noexcept(move(as_const(rng)) | views::common) == is_noexcept); + } else if constexpr (!is_view && enable_borrowed_range>) { + using S = decltype(ranges::subrange{move(as_const(rng))}); + constexpr bool is_noexcept = noexcept(S{move(as_const(rng))}); + + static_assert(same_as); + static_assert(noexcept(views::common(move(as_const(rng)))) == is_noexcept); + + static_assert(same_as); + static_assert(noexcept(move(as_const(rng)) | views::common) == is_noexcept); + } + } + + if constexpr (!is_common) { + // Validate deduction guide + using R = common_view; + same_as auto r = common_view{forward(rng)}; + if (!is_constant_evaluated()) { + assert(ranges::equal(r, expected)); + } + + // Validate common_view::size + static_assert(CanMemberSize == sized_range); + if constexpr (sized_range) { + assert(r.size() == static_cast>(size(expected))); + static_assert(noexcept(r.size()) == noexcept(size(rng))); + } + + static_assert(CanMemberSize == sized_range); + if constexpr (sized_range) { + assert(as_const(r).size() == static_cast>(size(expected))); + static_assert(noexcept(as_const(r).size()) == noexcept(size(as_const(rng)))); + } + + // Validate view_interface::empty and operator bool + const bool is_empty = ranges::empty(expected); + if (!is_constant_evaluated()) { + if constexpr (CanMemberEmpty) { + assert(r.empty() == is_empty); + assert(static_cast(r) == !is_empty); + } + + if constexpr (CanMemberEmpty) { + assert(as_const(r).empty() == is_empty); + assert(static_cast(as_const(r)) == !is_empty); + } + } + + // Validate common_view::begin and common_view::end + STATIC_ASSERT(CanMemberBegin); + STATIC_ASSERT(CanBegin == range); + STATIC_ASSERT(CanMemberEnd); + STATIC_ASSERT(CanEnd == range); + if (!is_constant_evaluated()) { + non_literal_parts(r, expected); + } + + // Validate view_interface::data + static_assert(!CanData); + static_assert(!CanData); + if (!is_constant_evaluated() && !is_empty) { + // Validate view_interface::operator[] + if constexpr (CanIndex) { + assert(r[0] == *begin(expected)); + } + + if constexpr (CanIndex) { + assert(as_const(r)[0] == *begin(expected)); + } + + // Validate view_interface::front and back + if constexpr (CanMemberFront) { + assert(r.front() == *begin(expected)); + } + + if constexpr (CanMemberFront) { + assert(as_const(r).front() == *begin(expected)); + } + + if constexpr (CanMemberBack) { + assert(r.back() == *prev(end(expected))); + } + + if constexpr (CanMemberBack) { + assert(as_const(r).back() == *prev(end(expected))); + } + } + + // Validate common_view::base() const& + static_assert(CanMemberBase == copy_constructible); + if constexpr (copy_constructible) { + 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() == *begin(expected)); + } + } + + // Validate common_view::base() && (NB: do this last since it leaves r moved-from) +#if !defined(__clang__) && !defined(__EDG__) // TRANSITION, DevCom-1159442 + (void) 42; +#endif // TRANSITION, DevCom-1159442 + same_as auto b2 = move(r).base(); + static_assert(noexcept(move(r).base()) == is_nothrow_move_constructible_v); + if (!is_empty) { + assert(*b2.begin() == *begin(expected)); + } + } + + return true; +} + +struct instantiator { + static constexpr int some_ints[] = {0, 1, 2}; + + template + static constexpr void call() { + if constexpr (copyable>) { + R r{some_ints}; + test_one(r, span{some_ints}); + } + } +}; + +int main() { + // Get full instantiation coverage + static_assert((test_in(), true)); + test_in(); +}