diff --git a/libcxx/include/__ranges/reverse_view.h b/libcxx/include/__ranges/reverse_view.h --- a/libcxx/include/__ranges/reverse_view.h +++ b/libcxx/include/__ranges/reverse_view.h @@ -19,8 +19,11 @@ #include <__ranges/concepts.h> #include <__ranges/enable_borrowed_range.h> #include <__ranges/non_propagating_cache.h> +#include <__ranges/range_adaptor.h> #include <__ranges/size.h> +#include <__ranges/subrange.h> #include <__ranges/view_interface.h> +#include <__utility/forward.h> #include <__utility/move.h> #include @@ -104,6 +107,80 @@ template inline constexpr bool enable_borrowed_range> = enable_borrowed_range<_Tp>; + + namespace views { + namespace __reverse { + template + constexpr bool __is_reverse_view = false; + + template + constexpr bool __is_reverse_view> = true; + + template + constexpr bool __is_sized_reverse_subrange = false; + + template + constexpr bool __is_sized_reverse_subrange, reverse_iterator<_Iter>, subrange_kind::sized>> = true; + + template + constexpr bool __is_unsized_reverse_subrange = false; + + template + constexpr bool __is_unsized_reverse_subrange, reverse_iterator<_Iter>, _Kind>> = _Kind == subrange_kind::unsized; + + template + struct __unwrapped_reverse_subrange { + using type = void; // avoid SFINAE-ing out the overload below -- let the concept requirements do it for better diagnostics + }; + + template + struct __unwrapped_reverse_subrange, reverse_iterator<_Iter>, _Kind>> { + using type = subrange<_Iter, _Iter, _Kind>; + }; + + struct __fn : __range_adaptor_closure<__fn> { + template + requires __is_reverse_view> + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI + constexpr auto operator()(_Range&& __range) const + noexcept(noexcept(_VSTD::forward<_Range>(__range).base())) + -> decltype( _VSTD::forward<_Range>(__range).base()) + { return _VSTD::forward<_Range>(__range).base(); } + + template>::type> + requires __is_sized_reverse_subrange> + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI + constexpr auto operator()(_Range&& __range) const + noexcept(noexcept(_UnwrappedSubrange(__range.end().base(), __range.begin().base(), __range.size()))) + -> decltype( _UnwrappedSubrange(__range.end().base(), __range.begin().base(), __range.size())) + { return _UnwrappedSubrange(__range.end().base(), __range.begin().base(), __range.size()); } + + template>::type> + requires __is_unsized_reverse_subrange> + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI + constexpr auto operator()(_Range&& __range) const + noexcept(noexcept(_UnwrappedSubrange(__range.end().base(), __range.begin().base()))) + -> decltype( _UnwrappedSubrange(__range.end().base(), __range.begin().base())) + { return _UnwrappedSubrange(__range.end().base(), __range.begin().base()); } + + template + requires (!__is_reverse_view> && + !__is_sized_reverse_subrange> && + !__is_unsized_reverse_subrange>) + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI + constexpr auto operator()(_Range&& __range) const + noexcept(noexcept(reverse_view{_VSTD::forward<_Range>(__range)})) + -> decltype( reverse_view{_VSTD::forward<_Range>(__range)}) + { return reverse_view{_VSTD::forward<_Range>(__range)}; } + }; + } + + inline namespace __cpo { + inline constexpr auto reverse = __reverse::__fn{}; + } + } // namespace views } // namespace ranges #endif // !defined(_LIBCPP_HAS_NO_RANGES) diff --git a/libcxx/test/std/ranges/range.adaptors/range.reverse/adaptor.nodiscard.verify.cpp b/libcxx/test/std/ranges/range.adaptors/range.reverse/adaptor.nodiscard.verify.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/ranges/range.adaptors/range.reverse/adaptor.nodiscard.verify.cpp @@ -0,0 +1,25 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: libcpp-no-concepts +// UNSUPPORTED: libcpp-has-no-incomplete-ranges +// REQUIRES: libc++ + +// Test the libc++ extension that std::views::reverse is marked as [[nodiscard]] to avoid +// the potential for user mistakenly thinking they're calling an algorithm. + +#include + +void test() { + int range[] = {1, 2, 3}; + + std::views::reverse(range); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}} + range | std::views::reverse; // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}} + std::views::reverse | std::views::reverse; // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}} +} diff --git a/libcxx/test/std/ranges/range.adaptors/range.reverse/adaptor.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.reverse/adaptor.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/ranges/range.adaptors/range.reverse/adaptor.pass.cpp @@ -0,0 +1,181 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: libcpp-no-concepts +// UNSUPPORTED: libcpp-has-no-incomplete-ranges + +// std::views::reverse + +#include + +#include +#include +#include +#include + +#include "types.h" + +template +concept CanBePiped = requires (View&& view, T&& t) { + { std::forward(view) | std::forward(t) }; +}; + +constexpr bool test() { + int buf[] = {1, 2, 3}; + + // views::reverse(x) is equivalent to x.base() if x is a reverse_view + { + { + BidirRange view(buf, buf + 3); + std::ranges::reverse_view reversed(view); + std::same_as auto result = std::views::reverse(reversed); + assert(result.begin_ == buf); + assert(result.end_ == buf + 3); + } + { + // Common use case is worth testing + BidirRange view(buf, buf + 3); + std::same_as auto result = std::views::reverse(std::views::reverse(view)); + assert(result.begin_ == buf); + assert(result.end_ == buf + 3); + } + } + + // views::reverse(x) is equivalent to subrange{end, begin, size} if x is a + // sized subrange over reverse iterators + { + using It = bidirectional_iterator; + using Subrange = std::ranges::subrange; + + using ReverseIt = std::reverse_iterator; + using ReverseSubrange = std::ranges::subrange; + + { + BidirRange view(buf, buf + 3); + ReverseSubrange subrange(ReverseIt(std::ranges::end(view)), ReverseIt(std::ranges::begin(view)), /* size */3); + std::same_as auto result = std::views::reverse(subrange); + assert(result.begin().base() == buf); + assert(result.end().base() == buf + 3); + } + { + // std::move into views::reverse + BidirRange view(buf, buf + 3); + ReverseSubrange subrange(ReverseIt(std::ranges::end(view)), ReverseIt(std::ranges::begin(view)), /* size */3); + std::same_as auto result = std::views::reverse(std::move(subrange)); + assert(result.begin().base() == buf); + assert(result.end().base() == buf + 3); + } + { + // with a const subrange + BidirRange view(buf, buf + 3); + ReverseSubrange const subrange(ReverseIt(std::ranges::end(view)), ReverseIt(std::ranges::begin(view)), /* size */3); + std::same_as auto result = std::views::reverse(subrange); + assert(result.begin().base() == buf); + assert(result.end().base() == buf + 3); + } + } + + // views::reverse(x) is equivalent to subrange{end, begin} if x is an + // unsized subrange over reverse iterators + { + using It = bidirectional_iterator; + using Subrange = std::ranges::subrange; + + using ReverseIt = std::reverse_iterator; + using ReverseSubrange = std::ranges::subrange; + + { + BidirRange view(buf, buf + 3); + ReverseSubrange subrange(ReverseIt(std::ranges::end(view)), ReverseIt(std::ranges::begin(view))); + std::same_as auto result = std::views::reverse(subrange); + assert(result.begin().base() == buf); + assert(result.end().base() == buf + 3); + } + { + // std::move into views::reverse + BidirRange view(buf, buf + 3); + ReverseSubrange subrange(ReverseIt(std::ranges::end(view)), ReverseIt(std::ranges::begin(view))); + std::same_as auto result = std::views::reverse(std::move(subrange)); + assert(result.begin().base() == buf); + assert(result.end().base() == buf + 3); + } + { + // with a const subrange + BidirRange view(buf, buf + 3); + ReverseSubrange const subrange(ReverseIt(std::ranges::end(view)), ReverseIt(std::ranges::begin(view))); + std::same_as auto result = std::views::reverse(subrange); + assert(result.begin().base() == buf); + assert(result.end().base() == buf + 3); + } + } + + // Otherwise, views::reverse(x) is equivalent to ranges::reverse_view{x} + { + BidirRange view(buf, buf + 3); + std::same_as> auto result = std::views::reverse(view); + assert(result.begin().base().base() == buf + 3); + assert(result.end().base().base() == buf); + } + + // Test that std::views::reverse is a range adaptor + { + // Test `v | views::reverse` + { + BidirRange view(buf, buf + 3); + std::same_as> auto result = view | std::views::reverse; + assert(result.begin().base().base() == buf + 3); + assert(result.end().base().base() == buf); + } + + // Test `adaptor | views::reverse` + { + BidirRange view(buf, buf + 3); + auto f = [](int i) { return i; }; + auto const partial = std::views::transform(f) | std::views::reverse; + using Result = std::ranges::reverse_view>; + std::same_as auto result = partial(view); + assert(result.begin().base().base().base() == buf + 3); + assert(result.end().base().base().base() == buf); + } + + // Test `views::reverse | adaptor` + { + BidirRange view(buf, buf + 3); + auto f = [](int i) { return i; }; + auto const partial = std::views::reverse | std::views::transform(f); + using Result = std::ranges::transform_view, decltype(f)>; + std::same_as auto result = partial(view); + assert(result.begin().base().base().base() == buf + 3); + assert(result.end().base().base().base() == buf); + } + + // Check SFINAE friendliness + { + struct NotABidirRange { }; + static_assert(!std::is_invocable_v); + static_assert(!std::is_invocable_v); + static_assert( CanBePiped); + static_assert( CanBePiped); + static_assert(!CanBePiped); + } + } + + { + static_assert(std::same_as); + } + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +}