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 @@ -14,6 +14,7 @@ #include <__ranges/access.h> #include <__ranges/size.h> #include <__utility/forward.h> +#include <__utility/priority_tag.h> #include #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) @@ -24,47 +25,53 @@ #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)); }; +namespace ranges { +namespace __empty { template - concept __can_compare_begin_end = - !__member_empty<_Tp> && - !__can_invoke_size<_Tp> && - requires(_Tp&& __t) { - bool(ranges::begin(__t) == ranges::end(__t)); + concept __basic_requirements = + requires (_Tp __t) { bool(__t.empty()); } || + requires (_Tp __t) { ranges::size(__t) == 0; } || + requires (_Tp __t) { { ranges::begin(__t) } -> forward_iterator; + bool(ranges::begin(__t) == ranges::end(__t)); }; struct __fn { - template <__member_empty _Tp> - [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr bool operator()(_Tp&& __t) const - noexcept(noexcept(bool(__t.empty()))) { - return __t.empty(); - } + template requires is_unbounded_array_v> + _LIBCPP_HIDE_FROM_ABI + static constexpr void __go(_Tp&& __t, __priority_tag<3>) = delete; + + template + _LIBCPP_HIDE_FROM_ABI + static constexpr auto __go(_Tp&& __t, __priority_tag<2>) + noexcept(noexcept(bool(__t.empty()))) + -> decltype( bool(__t.empty())) + { return bool(__t.empty()); } + + template + _LIBCPP_HIDE_FROM_ABI + static constexpr auto __go(_Tp&& __t, __priority_tag<1>) + noexcept(noexcept(ranges::size(__t) == 0)) + -> decltype( ranges::size(__t) == 0) + { return ranges::size(__t) == 0; } - template <__can_invoke_size _Tp> - [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr bool operator()(_Tp&& __t) const - noexcept(noexcept(ranges::size(_VSTD::forward<_Tp>(__t)))) { - return ranges::size(_VSTD::forward<_Tp>(__t)) == 0; - } + template + _LIBCPP_HIDE_FROM_ABI + static constexpr auto __go(_Tp&& __t, __priority_tag<0>) + noexcept(noexcept(bool(ranges::begin(__t) == ranges::end(__t)))) + -> decltype( bool(ranges::begin(__t) == ranges::end(__t))) + requires forward_iterator + { return bool(ranges::begin(__t) == ranges::end(__t)); } - template<__can_compare_begin_end _Tp> - [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr bool operator()(_Tp&& __t) const - noexcept(noexcept(bool(ranges::begin(__t) == ranges::end(__t)))) { - return ranges::begin(__t) == ranges::end(__t); - } + template + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI + constexpr decltype(auto) operator()(_Tp&& __t) const + noexcept(noexcept(__go(_VSTD::forward<_Tp>(__t), __priority_tag<3>()))) + requires __basic_requirements<_Tp&&> && + (requires { __go(_VSTD::forward<_Tp>(__t), __priority_tag<3>()); }) + { return __go(_VSTD::forward<_Tp>(__t), __priority_tag<3>()); } }; } @@ -72,7 +79,6 @@ inline constexpr auto empty = __empty::__fn{}; } // namespace __cpo } // namespace ranges -// clang-format off #endif // !defined(_LIBCPP_HAS_NO_RANGES) diff --git a/libcxx/test/libcxx/ranges/range.access/empty.verify.cpp b/libcxx/test/libcxx/ranges/range.access/empty.verify.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/ranges/range.access/empty.verify.cpp @@ -0,0 +1,24 @@ +//===----------------------------------------------------------------------===// +// +// 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::ranges::empty +// Substitution failure should give reasonably nice diagnostics. + +#include + +void f() { + // expected-error@*:* {{no matching function for call}} + // expected-note@*:* {{'bool(__t.empty())' would be invalid}} + // expected-note@*:* {{'ranges::size(__t) == 0' would be invalid}} + // expected-note@*:* {{'ranges::begin(__t)' would be invalid}} + std::ranges::empty(42); +} diff --git a/libcxx/test/libcxx/ranges/range.access/range.prim/empty.incomplete.verify.cpp b/libcxx/test/libcxx/ranges/range.access/range.prim/empty.incomplete.verify.cpp deleted file mode 100644 --- a/libcxx/test/libcxx/ranges/range.access/range.prim/empty.incomplete.verify.cpp +++ /dev/null @@ -1,53 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 - -// Test the libc++ specific behavior that we provide a better diagnostic when calling -// std::ranges::empty on an array of incomplete type. - -#include - -struct Incomplete; - -void f(Incomplete arr[]) { - // expected-error@*:* {{is SFINAE-unfriendly on arrays of an incomplete type.}} - // expected-error@*:* {{call to deleted function call operator in type}} - // expected-error@*:* {{attempt to use a deleted function}} - std::ranges::begin(arr); -} - -void f(Incomplete(&arr)[]) { - // expected-error@*:* {{is SFINAE-unfriendly on arrays of an incomplete type.}} - std::ranges::begin(arr); -} - -void f(Incomplete(&&arr)[]) { - // expected-error@*:* {{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}} - // 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 --- a/libcxx/test/std/ranges/range.access/range.prim/empty.pass.cpp +++ b/libcxx/test/std/ranges/range.access/range.prim/empty.pass.cpp @@ -15,11 +15,11 @@ #include #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); @@ -30,12 +30,27 @@ static_assert( std::is_invocable_v); static_assert( std::is_invocable_v); -struct NonConstSizeAndEmpty { - int size(); +struct Incomplete; +static_assert(!std::is_invocable_v); +static_assert(!std::is_invocable_v); +static_assert(!std::is_invocable_v); + +extern Incomplete array_of_incomplete[42]; +static_assert(!std::ranges::empty(array_of_incomplete)); +static_assert(!std::ranges::empty(std::move(array_of_incomplete))); +static_assert(!std::ranges::empty(std::as_const(array_of_incomplete))); +static_assert(!std::ranges::empty(static_cast(array_of_incomplete))); + +struct InputRangeWithoutSize { + cpp17_input_iterator begin() const; + cpp17_input_iterator end() const; +}; +static_assert(!std::is_invocable_v); + +struct NonConstEmpty { bool empty(); }; -static_assert(!std::is_invocable_v); -static_assert(!std::is_invocable_v); +static_assert(!std::is_invocable_v); struct HasMemberAndFunction { constexpr bool empty() const { return true; } @@ -49,7 +64,7 @@ static_assert(!std::is_invocable_v); struct BoolConvertible { - constexpr /*TODO: explicit*/ operator bool() noexcept(false) { return true; } + constexpr explicit operator bool() noexcept(false) { return true; } }; struct BoolConvertibleReturnType { constexpr BoolConvertible empty() noexcept { return {}; } @@ -148,9 +163,7 @@ assert(std::ranges::empty(e) == false); // e.empty() assert(std::ranges::empty(std::as_const(e)) == true); // e.begin() == e.end() -#if 0 // TODO FIXME assert(std::ranges::empty(EvilBeginEnd())); -#endif return true; }