diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -37,6 +37,7 @@ __nullptr __ranges/access.h __ranges/concepts.h + __ranges/empty.h __ranges/enable_borrowed_range.h __ranges/view.h __ranges/size.h diff --git a/libcxx/include/__ranges/empty.h b/libcxx/include/__ranges/empty.h new file mode 100644 --- /dev/null +++ b/libcxx/include/__ranges/empty.h @@ -0,0 +1,83 @@ +// -*- 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_EMPTY_H +#define _LIBCPP___RANGES_EMPTY_H + +#include <__config> +#include <__iterator/concepts.h> +#include <__ranges/size.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) + +// clang-format off +namespace ranges { +// [range.prim.empty] +namespace __empty { + template + concept __member_empty = requires(_Tp&& __t) { + bool(_VSTD::forward<_Tp>(__t).empty()); + }; + + template + concept __can_invoke_size = + !__member_empty<_Tp> && + requires(_Tp&& __t) { ranges::size(_VSTD::forward<_Tp>(__t)); }; + + template + concept __can_compare_begin_end = + !__can_invoke_size<_Tp> && + requires(_Tp&& __t) { + bool(ranges::begin(__t) == ranges::end(__t)); + { ranges::begin(__t) } -> forward_iterator; + }; + + struct __fn { + template <__member_empty _Tp> + [[nodiscard]] constexpr bool operator()(_Tp&& __t) const + noexcept(noexcept(bool(__t.empty()))) { + return __t.empty(); + } + + template <__can_invoke_size _Tp> + [[nodiscard]] constexpr bool operator()(_Tp&& __t) const + noexcept(noexcept(ranges::size(_VSTD::forward<_Tp>(__t)))) { + return ranges::size(_VSTD::forward<_Tp>(__t)) == 0; + } + + template<__can_compare_begin_end _Tp> + [[nodiscard]] constexpr bool operator()(_Tp&& __t) const + noexcept(noexcept(bool(ranges::begin(__t) == ranges::end(__t)))) { + return ranges::begin(__t) == ranges::end(__t); + } + }; +} + +inline namespace __cpo { + inline constexpr auto empty = __empty::__fn{}; +} // namespace __cpo +} // namespace ranges +// clang-format off + +#endif // !defined(_LIBCPP_HAS_NO_RANGES) + +_LIBCPP_END_NAMESPACE_STD + +_LIBCPP_POP_MACROS + +#endif // _LIBCPP___RANGES_EMPTY_H diff --git a/libcxx/include/ranges b/libcxx/include/ranges --- a/libcxx/include/ranges +++ b/libcxx/include/ranges @@ -78,6 +78,7 @@ #include <__config> #include <__ranges/access.h> #include <__ranges/concepts.h> +#include <__ranges/empty.h> #include <__ranges/enable_borrowed_range.h> #include <__ranges/view.h> #include <__ranges/size.h> diff --git a/libcxx/test/std/ranges/range.access/range.prim/empty.incomplete.verify.cpp b/libcxx/test/std/ranges/range.access/range.prim/empty.incomplete.verify.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/ranges/range.access/range.prim/empty.incomplete.verify.cpp @@ -0,0 +1,52 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// unspecified begin; + +#include + +struct Incomplete; + +void f(Incomplete arr[]) { + // expected-error@*:* {{"`std::ranges::begin` is SFINAE-unfriendly on arrays of an incomplete type."}} + // expected-error@*:* {{call to deleted function call operator in type 'const std::ranges::__begin::__fn'}} + // expected-error@*:* {{attempt to use a deleted function}} + std::ranges::begin(arr); +} + +void f(Incomplete(&arr)[]) { + // expected-error@*:* {{"`std::ranges::begin` is SFINAE-unfriendly on arrays of an incomplete type."}} + std::ranges::begin(arr); +} + +void f(Incomplete(&&arr)[]) { + // expected-error@*:* {{"`std::ranges::begin` is SFINAE-unfriendly on arrays of an incomplete type."}} + std::ranges::begin(arr); +} + +void f2(Incomplete arr[2]) { + // expected-error@*:* {{call to deleted function call operator in type 'const std::ranges::__begin::__fn'}} + // expected-error@*:* {{attempt to use a deleted function}} + std::ranges::begin(arr); +} + +void f(Incomplete(&arr)[2]) { + std::ranges::begin(arr); +} + +void f(Incomplete(&&arr)[2]) { + std::ranges::begin(arr); +} + +void f(Incomplete(&arr)[2][2]) { + std::ranges::begin(arr); +} diff --git a/libcxx/test/std/ranges/range.access/range.prim/empty.pass.cpp b/libcxx/test/std/ranges/range.access/range.prim/empty.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/ranges/range.access/range.prim/empty.pass.cpp @@ -0,0 +1,163 @@ +//===----------------------------------------------------------------------===// +// +// 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::empty + +#include + +#include +#include "test_macros.h" +#include "test_iterators.h" + +using RangeEmptyT = decltype(std::ranges::empty); +using RangeSizeT = decltype(std::ranges::size); + +static_assert(!std::is_invocable_v); +static_assert(!std::is_invocable_v); +static_assert(!std::is_invocable_v); +static_assert( std::is_invocable_v); +static_assert( std::is_invocable_v); +static_assert( std::is_invocable_v); +static_assert( std::is_invocable_v); +static_assert( std::is_invocable_v); + +struct NonConstSizeAndEmpty { + int size(); + bool empty(); +}; +static_assert(!std::is_invocable_v); +static_assert(!std::is_invocable_v); + +struct HasMemberAndFunction { + constexpr bool empty() const { return true; } + // We should never do ADL lookup for std::ranges::empty. + friend bool empty(const HasMemberAndFunction&) { return false; } +}; + +struct BadReturnType { + BadReturnType empty() { return {}; } +}; +static_assert(!std::is_invocable_v); + +struct BoolConvertible { + constexpr operator bool() noexcept(false) { return true; } +}; +struct BoolConvertibleReturnType { + constexpr BoolConvertible empty() noexcept { return {}; } +}; + +static_assert(!noexcept(std::ranges::empty(BoolConvertibleReturnType()))); + +constexpr bool testEmptyMember() { + HasMemberAndFunction a; + assert(std::ranges::empty(a) == true); + + BoolConvertibleReturnType b; + assert(std::ranges::empty(b) == true); + + return true; +} + +struct SizeMember { + size_t size_; + constexpr size_t size() const { return size_; } +}; + +struct SizeFunction { + size_t size_; + friend constexpr size_t size(SizeFunction sf) { return sf.size_; } +}; + +constexpr bool testUsingRangesSize() { + SizeMember a{1}; + assert(std::ranges::empty(a) == false); + SizeMember b{0}; + assert(std::ranges::empty(b) == true); + + SizeFunction c{1}; + assert(std::ranges::empty(c) == false); + SizeFunction d{0}; + assert(std::ranges::empty(d) == true); + + return true; +} + +struct other_forward_iterator : forward_iterator { }; + +struct sentinel { + constexpr bool operator==(std::input_or_output_iterator auto) const { return true; } +}; + +struct BeginEndNotSizedSentinel { + friend constexpr forward_iterator begin(BeginEndNotSizedSentinel) { return {}; } + friend constexpr sentinel end(BeginEndNotSizedSentinel) { return {}; } +}; +static_assert(!std::is_invocable_v); + +struct InvalidMinusBeginEnd { + friend constexpr random_access_iterator begin(InvalidMinusBeginEnd) { return {}; } + friend constexpr sentinel end(InvalidMinusBeginEnd) { return {}; } +}; + +// Int is integer-like, but it is not other_forward_iterator's difference_type. +constexpr short operator-(sentinel, random_access_iterator) { return 2; } +constexpr short operator-(random_access_iterator, sentinel) { return 2; } +static_assert(!std::is_invocable_v); + +// This type will use ranges::size. +struct IntPtrBeginAndEnd { + int buff[8]; + constexpr int* begin() { return buff; } + constexpr int* end() { return buff + 8; } +}; +static_assert(std::is_invocable_v); + +// size is disabled here, and it isn't sized_sentinel_for, so we have to compare begin +// and end again. +struct DisabledSizeRangeWithBeginEnd { + friend constexpr forward_iterator begin(DisabledSizeRangeWithBeginEnd) { return {}; } + friend constexpr sentinel end(DisabledSizeRangeWithBeginEnd) { return {}; } + constexpr size_t size() const { return 1; } +}; + +template<> +inline constexpr bool std::disable_sized_range = true; +static_assert(!std::is_invocable_v); + +constexpr bool testBeginEqualsEnd() { + BeginEndNotSizedSentinel a; + assert(std::ranges::empty(a) == true); + + InvalidMinusBeginEnd b; + assert(std::ranges::empty(b) == true); + + IntPtrBeginAndEnd c; + assert(std::ranges::empty(c) == false); + + DisabledSizeRangeWithBeginEnd d; + assert(std::ranges::empty(d) == true); + + return true; +} + +int main(int, char**) { + testEmptyMember(); + static_assert(testEmptyMember()); + + testUsingRangesSize(); + static_assert(testUsingRangesSize()); + + testBeginEqualsEnd(); + static_assert(testBeginEqualsEnd()); + + return 0; +}