diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -41,6 +41,7 @@ __ranges/data.h __ranges/enable_borrowed_range.h __ranges/size.h + __ranges/view_interface.h __split_buffer __sso_allocator __std_stream diff --git a/libcxx/include/__ranges/access.h b/libcxx/include/__ranges/access.h --- a/libcxx/include/__ranges/access.h +++ b/libcxx/include/__ranges/access.h @@ -1,5 +1,5 @@ // -*- C++ -*- -//===------------------------ __ranges/begin.h ----------------------------===// +//===------------------------ __ranges/access.h ----------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. diff --git a/libcxx/include/__ranges/concepts.h b/libcxx/include/__ranges/concepts.h --- a/libcxx/include/__ranges/concepts.h +++ b/libcxx/include/__ranges/concepts.h @@ -11,8 +11,7 @@ #include <__config> #include <__iterator/concepts.h> -#include <__ranges/begin.h> -#include <__ranges/end.h> +#include <__ranges/access.h> #include #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) @@ -36,7 +35,7 @@ ranges::end(__t); }; - // `iterator_t` defined in <__ranges/begin.h> + // `iterator_t` defined in <__ranges/access.h> template using sentinel_t = decltype(ranges::end(declval<_Rp&>())); diff --git a/libcxx/include/__ranges/data.h b/libcxx/include/__ranges/data.h --- a/libcxx/include/__ranges/data.h +++ b/libcxx/include/__ranges/data.h @@ -13,7 +13,8 @@ #include #include <__iterator/iterator_traits.h> #include <__iterator/concepts.h> -#include <__ranges/begin.h> +#include +#include <__ranges/access.h> #include #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) @@ -54,6 +55,7 @@ template concept __unqualified_begin = + __member_data<_Tp> && __not_incomplete_array<_Tp> && requires(_Tp&& __t) { { _VSTD::forward<_Tp>(__t) } -> __not_invalid_rvalue; diff --git a/libcxx/include/__ranges/empty.h b/libcxx/include/__ranges/empty.h --- a/libcxx/include/__ranges/empty.h +++ b/libcxx/include/__ranges/empty.h @@ -41,6 +41,7 @@ template concept __can_compare_begin_end = + !__member_empty<_Tp> && !__can_invoke_size<_Tp> && requires(_Tp&& __t) { bool(ranges::begin(__t) == ranges::end(__t)); diff --git a/libcxx/include/__ranges/view_interface.h b/libcxx/include/__ranges/view_interface.h new file mode 100644 --- /dev/null +++ b/libcxx/include/__ranges/view_interface.h @@ -0,0 +1,188 @@ +// -*- 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_VIEW_INTERFACE_H +#define _LIBCPP___RANGES_VIEW_INTERFACE_H + +#include <__config> +#include <__iterator/iterator_traits.h> +#include <__iterator/concepts.h> +#include <__ranges/access.h> +#include + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +#pragma GCC system_header +#endif + +_LIBCPP_PUSH_MACROS +#include <__undef_macros> + +_LIBCPP_BEGIN_NAMESPACE_STD + +#if !defined(_LIBCPP_HAS_NO_RANGES) + +namespace ranges { + +struct view_base { }; + +template +inline constexpr bool enable_view = derived_from<_Tp, view_base>; + +template +concept view = + range<_Tp> && + movable<_Tp> && + default_initializable<_Tp> && + enable_view<_Tp>; + + +template +concept __can_empty = requires(_Tp __t) { ranges::empty(__t); }; + +template + requires is_class_v<_Derived> && same_as<_Derived, remove_cv_t<_Derived>> +class view_interface : public view_base { + [[nodiscard]] constexpr _Derived& __derived() noexcept { + return static_cast<_Derived&>(*this); + } + + [[nodiscard]] constexpr _Derived const& __derived() const noexcept { + return static_cast<_Derived const&>(*this); + } + +public: + template + [[nodiscard]] constexpr bool empty() + noexcept(noexcept(ranges::begin(__derived()) == ranges::end(__derived()))) + requires forward_range<_D2> + { + return ranges::begin(__derived()) == ranges::end(__derived()); + } + + template + [[nodiscard]] constexpr bool empty() const + noexcept(noexcept(ranges::begin(__derived()) == ranges::end(__derived()))) + requires forward_range + { + return ranges::begin(__derived()) == ranges::end(__derived()); + } + + template + [[nodiscard]] constexpr explicit operator bool() + noexcept(noexcept(ranges::empty(declval<_D2>()))) + requires __can_empty<_D2> + { + return !ranges::empty(__derived()); + } + + template + [[nodiscard]] constexpr explicit operator bool() const + noexcept(noexcept(ranges::empty(declval()))) + requires __can_empty + { + return !ranges::empty(__derived()); + } + + template + [[nodiscard]] constexpr auto data() + noexcept(noexcept(_VSTD::to_address(ranges::begin(__derived())))) + requires contiguous_iterator> + { + return _VSTD::to_address(ranges::begin(__derived())); + } + + template + [[nodiscard]] constexpr auto data() const + noexcept(noexcept(_VSTD::to_address(ranges::begin(__derived())))) + requires range && contiguous_iterator> + { + return _VSTD::to_address(ranges::begin(__derived())); + } + + template + [[nodiscard]] constexpr auto size() + noexcept(noexcept(ranges::end(__derived()) - ranges::begin(__derived()))) + requires forward_range<_D2> + && sized_sentinel_for, iterator_t<_D2>> + { + return ranges::end(__derived()) - ranges::begin(__derived()); + } + + template + [[nodiscard]] constexpr auto size() const + noexcept(noexcept(ranges::end(__derived()) - ranges::begin(__derived()))) + requires forward_range + && sized_sentinel_for, iterator_t> + { + return ranges::end(__derived()) - ranges::begin(__derived()); + } + + template + [[nodiscard]] constexpr decltype(auto) front() + noexcept(noexcept(*ranges::begin(__derived()))) + requires forward_range<_D2> + { + _LIBCPP_ASSERT(!empty(), + "Precondition `!empty()` not satisfied. `.front()` called on an empty view."); + return *ranges::begin(__derived()); + } + + template + [[nodiscard]] constexpr decltype(auto) front() const + noexcept(noexcept(*ranges::begin(__derived()))) + requires forward_range + { + _LIBCPP_ASSERT(!empty(), + "Precondition `!empty()` not satisfied. `.front()` called on an empty view."); + return *ranges::begin(__derived()); + } + + template + [[nodiscard]] constexpr decltype(auto) back() + noexcept(noexcept(*(--ranges::end(__derived())))) + requires bidirectional_range<_D2> && common_range<_D2> + { + _LIBCPP_ASSERT(!empty(), + "Precondition `!empty()` not satisfied. `.back()` called on an empty view."); + return *(--ranges::end(__derived())); + } + + template + [[nodiscard]] constexpr decltype(auto) back() const + noexcept(noexcept(*(--ranges::end(__derived())))) + requires bidirectional_range && common_range + { + _LIBCPP_ASSERT(!empty(), + "Precondition `!empty()` not satisfied. `.back()` called on an empty view."); + return *(--ranges::end(__derived())); + } + + template + [[nodiscard]] constexpr decltype(auto) operator[](range_difference_t<_RARange> __index) + noexcept(noexcept(ranges::begin(__derived())[__index])) + { + return ranges::begin(__derived())[__index]; + } + + template + [[nodiscard]] constexpr decltype(auto) operator[](range_difference_t<_RARange> __index) const + noexcept(noexcept(ranges::begin(__derived())[__index])) + { + return ranges::begin(__derived())[__index]; + } +}; + +} + +#endif // !defined(_LIBCPP_HAS_NO_RANGES) + +_LIBCPP_END_NAMESPACE_STD + +_LIBCPP_POP_MACROS + +#endif // _LIBCPP___RANGES_VIEW_INTERFACE_H diff --git a/libcxx/include/ranges b/libcxx/include/ranges --- a/libcxx/include/ranges +++ b/libcxx/include/ranges @@ -67,6 +67,7 @@ #include <__ranges/data.h> #include <__ranges/enable_borrowed_range.h> #include <__ranges/size.h> +#include <__ranges/view_interface.h> #include // Required by the standard. #include // Required by the standard. #include // Required by the standard. diff --git a/libcxx/test/std/ranges/range.utility/view.interface/view.interface.pass.cpp b/libcxx/test/std/ranges/range.utility/view.interface/view.interface.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/ranges/range.utility/view.interface/view.interface.pass.cpp @@ -0,0 +1,282 @@ +//===----------------------------------------------------------------------===// +// +// 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: gcc-10 +// XFAIL: msvc && clang + +// std::ranges::size + +#include + +#include +#include "test_macros.h" +#include "test_iterators.h" + +namespace ranges = std::ranges; + +template +concept ValidViewInterfaceType = requires { typename ranges::view_interface; }; + +struct Empty { }; + +static_assert(!ValidViewInterfaceType); +static_assert(!ValidViewInterfaceType); +static_assert(!ValidViewInterfaceType); +static_assert(!ValidViewInterfaceType); +static_assert(!ValidViewInterfaceType); +static_assert( ValidViewInterfaceType); + +static_assert(std::derived_from, ranges::view_base>); + +using InputIter = input_iterator; + +struct InputRange : ranges::view_interface { + int buff[8] = {0, 1, 2, 3, 4, 5, 6, 7}; + constexpr InputIter begin() const { return InputIter(buff); } + constexpr InputIter end() const { return InputIter(buff + 8); } +}; + +struct NotSizedSentinel { + using I = int*; + using value_type = std::iter_value_t; + using difference_type = std::iter_difference_t; + using iterator_concept = std::forward_iterator_tag; + + NotSizedSentinel() = default; + explicit constexpr NotSizedSentinel(I); + + constexpr int &operator*() const { return *value; }; + NotSizedSentinel& operator++(); + NotSizedSentinel operator++(int); + bool operator==(NotSizedSentinel const&) const; + + int *value; +}; +static_assert(std::forward_iterator); + +using ForwardIter = forward_iterator; + +// So that we conform to sized_sentinel_for. +constexpr std::ptrdiff_t operator-(const ForwardIter& x, const ForwardIter& y) { + return x.base() - y.base(); +} + +struct ForwardRange : ranges::view_interface { + int buff[8] = {0, 1, 2, 3, 4, 5, 6, 7}; + constexpr ForwardIter begin() const { return ForwardIter(const_cast(buff)); } + constexpr ForwardIter end() const { return ForwardIter(const_cast(buff) + 8); } +}; + +struct MoveOnlyForwardRange : ranges::view_interface { + int buff[8] = {0, 1, 2, 3, 4, 5, 6, 7}; + MoveOnlyForwardRange(MoveOnlyForwardRange const&) = delete; + MoveOnlyForwardRange(MoveOnlyForwardRange &&) = default; + MoveOnlyForwardRange() = default; + constexpr ForwardIter begin() const { return ForwardIter(const_cast(buff)); } + constexpr ForwardIter end() const { return ForwardIter(const_cast(buff) + 8); } +}; + +struct EmptyIsTrue : ranges::view_interface { + int buff[8] = {0, 1, 2, 3, 4, 5, 6, 7}; + constexpr ForwardIter begin() const { return ForwardIter(const_cast(buff)); } + constexpr ForwardIter end() const { return ForwardIter(const_cast(buff) + 8); } + constexpr bool empty() const { return true; } +}; + +struct SizeIsTen : ranges::view_interface { + int buff[8] = {0, 1, 2, 3, 4, 5, 6, 7}; + constexpr ForwardIter begin() const { return ForwardIter(const_cast(buff)); } + constexpr ForwardIter end() const { return ForwardIter(const_cast(buff) + 8); } + constexpr size_t size() const { return 10; } +}; + +using RAIter = random_access_iterator; + +struct RARange : ranges::view_interface { + int buff[8] = {0, 1, 2, 3, 4, 5, 6, 7}; + constexpr RAIter begin() const { return RAIter(const_cast(buff)); } + constexpr RAIter end() const { return RAIter(const_cast(buff) + 8); } +}; + +using ContIter = contiguous_iterator; + +struct ContRange : ranges::view_interface { + int buff[8] = {0, 1, 2, 3, 4, 5, 6, 7}; + constexpr ContIter begin() const { return ContIter(buff); } + constexpr ContIter end() const { return ContIter(buff + 8); } +}; + +struct DataIsNull : ranges::view_interface { + int buff[8] = {0, 1, 2, 3, 4, 5, 6, 7}; + constexpr ContIter begin() const { return ContIter(buff); } + constexpr ContIter end() const { return ContIter(buff + 8); } + constexpr const int *data() const { return nullptr; } +}; + +template +concept EmptyInvocable = requires (T const& obj) { obj.empty(); }; + +template +concept BoolOpInvocable = requires (T const& obj) { bool(obj); }; + +constexpr bool testEmpty() { + static_assert(!EmptyInvocable); + static_assert( EmptyInvocable); + + static_assert(!BoolOpInvocable); + static_assert( BoolOpInvocable); + + ForwardRange forwardRange; + assert(!forwardRange.empty()); + assert(!static_cast(forwardRange).empty()); + + assert(forwardRange); + assert(static_cast(forwardRange)); + + assert(!ranges::empty(forwardRange)); + assert(!ranges::empty(static_cast(forwardRange))); + + EmptyIsTrue emptyTrue; + assert(emptyTrue.empty()); + assert(static_cast(emptyTrue).empty()); + assert(!emptyTrue.ranges::view_interface::empty()); + + assert(!emptyTrue); + assert(!static_cast(emptyTrue)); + assert(!emptyTrue.ranges::view_interface::operator bool()); + + assert(ranges::empty(emptyTrue)); + assert(ranges::empty(static_cast(emptyTrue))); + + // Try calling empty on an rvalue. + MoveOnlyForwardRange moveOnly; + assert(!std::move(moveOnly).empty()); + + return true; +} + +template +concept DataInvocable = requires (T const& obj) { obj.data(); }; + +constexpr bool testData() { + static_assert(!DataInvocable); + static_assert( DataInvocable); + + ContRange contiguous; + assert(contiguous.data() == contiguous.buff); + assert(static_cast(contiguous).data() == contiguous.buff); + + assert(ranges::data(contiguous) == contiguous.buff); + assert(ranges::data(static_cast(contiguous)) == contiguous.buff); + + DataIsNull dataNull; + assert(dataNull.data() == nullptr); + assert(static_cast(dataNull).data() == nullptr); + assert(dataNull.ranges::view_interface::data() == dataNull.buff); + +// TODO: why is the child member picked? +// assert(ranges::data(dataNull) == nullptr); +// assert(ranges::data(static_cast(dataNull)) == nullptr); + + return true; +} + +template +concept SizeInvocable = requires (T const& obj) { obj.size(); }; + +constexpr bool testSize() { + static_assert(!SizeInvocable); + static_assert(!SizeInvocable); + static_assert( SizeInvocable); + + ForwardRange forwardRange; + assert(forwardRange.size() == 8); + assert(static_cast(forwardRange).size() == 8); + + assert(ranges::size(forwardRange) == 8); + assert(ranges::size(static_cast(forwardRange)) == 8); + + SizeIsTen sizeTen; + assert(sizeTen.size() == 10); + assert(static_cast(sizeTen).size() == 10); + assert(sizeTen.ranges::view_interface::size() == 8); + + assert(ranges::size(sizeTen) == 10); + assert(ranges::size(static_cast(sizeTen)) == 10); + + return true; +} + +template +concept SubscriptInvocable = requires (T const& obj, size_t n) { obj[n]; }; + +constexpr bool testSubscript() { + static_assert(!SubscriptInvocable); + static_assert( SubscriptInvocable); + + RARange randomAccess; + assert(randomAccess[2] == 2); + assert(static_cast(randomAccess)[2] == 2); + randomAccess[2] = 3; + assert(randomAccess[2] == 3); + + return true; +} + +template +concept FrontInvocable = requires (T const& obj) { obj.front(); }; + +template +concept BackInvocable = requires (T const& obj) { obj.back(); }; + +constexpr bool testFrontBack() { + static_assert(!FrontInvocable); + static_assert( FrontInvocable); + static_assert(!BackInvocable); + static_assert( BackInvocable); + + ForwardRange forwardRange; + assert(forwardRange.front() == 0); + assert(static_cast(forwardRange).front() == 0); + forwardRange.front() = 2; + assert(forwardRange.front() == 2); + + RARange randomAccess; + assert(randomAccess.front() == 0); + assert(static_cast(randomAccess).front() == 0); + randomAccess.front() = 2; + assert(randomAccess.front() == 2); + + assert(randomAccess.back() == 7); + assert(static_cast(randomAccess).back() == 7); + randomAccess.back() = 2; + assert(randomAccess.back() == 2); + + return true; +} + +int main(int, char**) { + testEmpty(); + static_assert(testEmpty()); + + testData(); + static_assert(testData()); + + testSize(); + static_assert(testSize()); + + testSubscript(); + static_assert(testSubscript()); + + testFrontBack(); + static_assert(testFrontBack()); + + return 0; +}