diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -48,6 +48,7 @@ __ranges/data.h __ranges/empty.h __ranges/enable_borrowed_range.h + __ranges/view_interface.h __ranges/view.h __ranges/size.h __split_buffer 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,181 @@ +// -*- 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/concepts.h> +#include <__iterator/iterator_traits.h> +#include <__iterator/prev.h> +#include <__ranges/access.h> +#include <__ranges/empty.h> +#include <__ranges/view.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 { + +template +concept __can_empty = requires(_Tp __t) { ranges::empty(__t); }; + +template +void __implicitly_convert_to(type_identity_t<_Tp>) noexcept; + +template + requires is_class_v<_Derived> && same_as<_Derived, remove_cv_t<_Derived>> +class view_interface : public view_base { + constexpr _Derived& __derived() noexcept { + return static_cast<_Derived&>(*this); + } + + constexpr _Derived const& __derived() const noexcept { + return static_cast<_Derived const&>(*this); + } + +public: + template + [[nodiscard]] constexpr bool empty() + noexcept(noexcept(__implicitly_convert_to(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(__implicitly_convert_to(ranges::begin(__derived()) == ranges::end(__derived())))) + requires forward_range + { + return ranges::begin(__derived()) == ranges::end(__derived()); + } + + template + constexpr explicit operator bool() + noexcept(noexcept(ranges::empty(declval<_D2>()))) + requires __can_empty<_D2> + { + return !ranges::empty(__derived()); + } + + template + constexpr explicit operator bool() const + noexcept(noexcept(ranges::empty(declval()))) + requires __can_empty + { + return !ranges::empty(__derived()); + } + + template + constexpr auto data() + noexcept(noexcept(_VSTD::to_address(ranges::begin(__derived())))) + requires contiguous_iterator> + { + return _VSTD::to_address(ranges::begin(__derived())); + } + + template + constexpr auto data() const + noexcept(noexcept(_VSTD::to_address(ranges::begin(__derived())))) + requires range && contiguous_iterator> + { + return _VSTD::to_address(ranges::begin(__derived())); + } + + template + 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 + 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 + 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 + 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 + 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 + constexpr decltype(auto) back() const + noexcept(noexcept(*ranges::prev(ranges::end(__derived())))) + requires bidirectional_range && common_range + { + _LIBCPP_ASSERT(!empty(), + "Precondition `!empty()` not satisfied. `.back()` called on an empty view."); + return *ranges::prev(ranges::end(__derived())); + } + + template + constexpr decltype(auto) operator[](range_difference_t<_RARange> __index) + noexcept(noexcept(ranges::begin(__derived())[__index])) + { + return ranges::begin(__derived())[__index]; + } + + template + 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 @@ -89,6 +89,7 @@ #include <__ranges/empty.h> #include <__ranges/enable_borrowed_range.h> #include <__ranges/size.h> +#include <__ranges/view_interface.h> #include <__ranges/view.h> #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,314 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// std::ranges::size + +#include + +#include +#include "test_macros.h" +#include "test_iterators.h" + +template +concept ValidViewInterfaceType = requires { typename std::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, std::ranges::view_base>); + +using InputIter = cpp20_input_iterator; + +struct InputRange : std::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 : std::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 : std::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 : std::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 : std::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 : std::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 : std::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 : std::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 +struct BoolConvertibleComparison : std::ranges::view_interface> { + struct ResultType { + bool value; + constexpr operator bool() const noexcept(IsNoexcept) { return value; } + }; + + struct SentinelType { + int *base; + SentinelType() = default; + explicit constexpr SentinelType(int *base) : base(base) {} + friend constexpr ResultType operator==(ForwardIter const& iter, SentinelType const& sent) noexcept { return {iter.base() == sent.base}; } + friend constexpr ResultType operator==(SentinelType const& sent, ForwardIter const& iter) noexcept { return {iter.base() == sent.base}; } + friend constexpr ResultType operator!=(ForwardIter const& iter, SentinelType const& sent) noexcept { return {iter.base() != sent.base}; } + friend constexpr ResultType operator!=(SentinelType const& sent, ForwardIter const& iter) noexcept { return {iter.base() != sent.base}; } + }; + + int buff[8] = {0, 1, 2, 3, 4, 5, 6, 7}; + constexpr ForwardIter begin() const noexcept { return ForwardIter(const_cast(buff)); } + constexpr SentinelType end() const noexcept { return SentinelType(const_cast(buff) + 8); } +}; + +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(!std::ranges::empty(forwardRange)); + assert(!std::ranges::empty(static_cast(forwardRange))); + + EmptyIsTrue emptyTrue; + assert(emptyTrue.empty()); + assert(static_cast(emptyTrue).empty()); + assert(!emptyTrue.std::ranges::view_interface::empty()); + + assert(!emptyTrue); + assert(!static_cast(emptyTrue)); + assert(!emptyTrue.std::ranges::view_interface::operator bool()); + + assert(std::ranges::empty(emptyTrue)); + assert(std::ranges::empty(static_cast(emptyTrue))); + + // Try calling empty on an rvalue. + MoveOnlyForwardRange moveOnly; + assert(!std::move(moveOnly).empty()); + + BoolConvertibleComparison boolConv; + BoolConvertibleComparison boolConv2; + static_assert(noexcept(boolConv.empty())); + static_assert(!noexcept(boolConv2.empty())); + + assert(!boolConv.empty()); + assert(!static_cast const&>(boolConv).empty()); + + assert(boolConv); + assert(static_cast const&>(boolConv)); + + assert(!std::ranges::empty(boolConv)); + assert(!std::ranges::empty(static_cast const&>(boolConv))); + + 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(std::ranges::data(contiguous) == contiguous.buff); + assert(std::ranges::data(static_cast(contiguous)) == contiguous.buff); + + DataIsNull dataNull; + assert(dataNull.data() == nullptr); + assert(static_cast(dataNull).data() == nullptr); + assert(dataNull.std::ranges::view_interface::data() == dataNull.buff); + + assert(std::ranges::data(dataNull) == nullptr); + assert(std::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(std::ranges::size(forwardRange) == 8); + assert(std::ranges::size(static_cast(forwardRange)) == 8); + + SizeIsTen sizeTen; + assert(sizeTen.size() == 10); + assert(static_cast(sizeTen).size() == 10); + assert(sizeTen.std::ranges::view_interface::size() == 8); + + assert(std::ranges::size(sizeTen) == 10); + assert(std::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; +}