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/concepts.h __ranges/end.h __ranges/enable_borrowed_range.h + __ranges/size.h __split_buffer __sso_allocator __std_stream diff --git a/libcxx/include/__ranges/size.h b/libcxx/include/__ranges/size.h new file mode 100644 --- /dev/null +++ b/libcxx/include/__ranges/size.h @@ -0,0 +1,125 @@ +// -*- C++ -*- +//===------------------------ __ranges/end.h ------------------------------===// +// +// 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_SIZE_H +#define _LIBCPP_RANGES_SIZE_H + +#include <__config> +#include <__iterator/iterator_traits.h> +#include <__iterator/concepts.h> +#include <__ranges/begin.h> +#include <__ranges/end.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) + +template +inline constexpr bool disable_sized_sentinel_for = false; + +template +concept sized_sentinel_for = + sentinel_for<_Sp, _Ip> && + !disable_sized_sentinel_for, remove_cv_t<_Ip>> && + requires(const _Ip& __i, const _Sp& __s) { + { __s - __i } -> same_as>; + { __i - __s } -> same_as>; + }; + +template +inline constexpr bool disable_sized_range = false; + +// clang-format off +namespace ranges { +// [range.prim.size] +namespace __size { + +void size(auto&) = delete; +void size(const auto&) = delete; + +template +concept __size_enabled = !disable_sized_range>; + +template +concept __member_size = __size_enabled<_Tp> && requires(_Tp&& __t) { + { _VSTD::__decay_copy(_VSTD::forward<_Tp>(__t).size()) } -> integral; +}; + +template +concept __unqualified_size = + __size_enabled<_Tp> && + !__member_size<_Tp> && + __class_or_enum> && + requires(_Tp&& __t) { + { _VSTD::__decay_copy(size(_VSTD::forward<_Tp>(__t))) } -> integral; + }; + +template +concept __difference = + !__member_size<_Tp> && + !__unqualified_size<_Tp> && + __class_or_enum> && + requires(_Tp&& __t) { + { ranges::begin(__t) } -> forward_iterator; + { ranges::end(__t) } -> sized_sentinel_for()))>; + }; + +struct __fn { + template + [[nodiscard]] constexpr size_t operator()(_Tp (&&)[_Sz]) const noexcept { + return _Sz; + } + + template + [[nodiscard]] constexpr size_t operator()(_Tp (&)[_Sz]) const noexcept { + return _Sz; + } + + template <__member_size _Tp> + [[nodiscard]] constexpr integral auto operator()(_Tp&& __t) const + noexcept(noexcept(_VSTD::forward<_Tp>(__t).size())) { + return _VSTD::forward<_Tp>(__t).size(); + } + + template <__unqualified_size _Tp> + [[nodiscard]] constexpr integral auto operator()(_Tp&& __t) const + noexcept(noexcept(size(_VSTD::forward<_Tp>(__t)))) { + return size(_VSTD::forward<_Tp>(__t)); + } + + template<__difference _Tp> + [[nodiscard]] constexpr integral auto operator()(_Tp&& __t) const + noexcept(noexcept(ranges::end(__t) - ranges::begin(__t))) { + using _Unsigned = make_unsigned_t>>; + return static_cast<_Unsigned>(ranges::end(__t) - ranges::begin(__t)); + } +}; +} + +inline namespace __cpo { +inline constexpr const auto size = __size::__fn{}; +} // namespace __cpo +} // namespace ranges + +// clang-format off + +#endif // !defined(_LIBCPP_HAS_NO_RANGES) + +_LIBCPP_END_NAMESPACE_STD + +_LIBCPP_POP_MACROS + +#endif // _LIBCPP_RANGES_SIZE_H diff --git a/libcxx/include/ranges b/libcxx/include/ranges --- a/libcxx/include/ranges +++ b/libcxx/include/ranges @@ -61,6 +61,7 @@ #include <__ranges/concepts.h> #include <__ranges/end.h> #include <__ranges/enable_borrowed_range.h> +#include <__ranges/size.h> #include // Required by the standard. #include // Required by the standard. #include // Required by the standard. diff --git a/libcxx/test/std/ranges/range.access/range.prim/size.pass.cpp b/libcxx/test/std/ranges/range.access/range.prim/size.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/ranges/range.access/range.prim/size.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 + +// std::ranges::size + +#include + +#include +#include "test_macros.h" +#include "test_iterators.h" + +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); + +struct SizeMember { + constexpr size_t size() { return 42; } +}; + +static_assert(!std::is_invocable_v); + +struct SizeFunction { + friend constexpr size_t size(SizeFunction) { return 42; } +}; + +// Make sure the size member is preferred. +struct SizeMemberAndFunction { + constexpr size_t size() { return 42; } + friend constexpr size_t size(SizeMemberAndFunction) { return 0; } +}; + +bool constexpr testArrayType() { + int a[4]; + int b[1]; + SizeMember c[4]; + SizeFunction d[4]; + + assert(std::ranges::size(a) == 4); + assert(std::ranges::size(b) == 1); + assert(std::ranges::size(c) == 4); + assert(std::ranges::size(d) == 4); + + return true; +} + +struct SizeMemberConst { + constexpr size_t size() const { return 42; } +}; + +struct SizeMemberSigned { + constexpr long size() { return 42; } +}; + +bool constexpr testHasSizeMember() { + assert(std::ranges::size(SizeMember()) == 42); + + const SizeMemberConst sizeMemberConst; + assert(std::ranges::size(sizeMemberConst) == 42); + + assert(std::ranges::size(SizeMemberAndFunction()) == 42); + + assert(std::ranges::size(SizeMemberSigned()) == 42); + ASSERT_SAME_TYPE(decltype(std::ranges::size(SizeMemberSigned())), long); + + return true; +} + +struct MoveOnlySizeFunction { + MoveOnlySizeFunction() = default; + MoveOnlySizeFunction(MoveOnlySizeFunction &&) = default; + MoveOnlySizeFunction(MoveOnlySizeFunction const&) = delete; + + friend constexpr size_t size(MoveOnlySizeFunction) { return 42; } +}; + +enum EnumSizeFunction { + a, b +}; + +constexpr size_t size(EnumSizeFunction) { return 42; } + +struct SizeFunctionConst { + friend constexpr size_t size(const SizeFunctionConst) { return 42; } +}; + +struct SizeFunctionRef { + friend constexpr size_t size(SizeFunctionRef&) { return 42; } +}; + +struct SizeFunctionConstRef { + friend constexpr size_t size(SizeFunctionConstRef const&) { return 42; } +}; + +struct SizeFunctionSigned { + friend constexpr long size(SizeFunctionSigned) { return 42; } +}; + +bool constexpr testHasSizeFunction() { + assert(std::ranges::size(SizeFunction()) == 42); + assert(std::ranges::size(MoveOnlySizeFunction()) == 42); + assert(std::ranges::size(EnumSizeFunction()) == 42); + assert(std::ranges::size(SizeFunctionConst()) == 42); + + SizeFunctionRef a; + assert(std::ranges::size(a) == 42); + + const SizeFunctionConstRef b; + assert(std::ranges::size(b) == 42); + + assert(std::ranges::size(SizeFunctionSigned()) == 42); + ASSERT_SAME_TYPE(decltype(std::ranges::size(SizeFunctionSigned())), long); + + return true; +} + +struct Empty { }; +static_assert(!std::is_invocable_v); + +struct InvalidReturnTypeMember { + Empty size(); +}; + +struct InvalidReturnTypeFunction { + friend Empty size(InvalidReturnTypeFunction); +}; + +struct Convertible { + operator size_t(); +}; + +struct ConvertibleReturnTypeMember { + Convertible size(); +}; + +struct ConvertibleReturnTypeFunction { + friend Convertible size(ConvertibleReturnTypeFunction); +}; + +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 SizeMemberDisabled { + size_t size() { return 42; } +}; + +template<> +inline constexpr bool std::disable_sized_range = true; + +struct ConstSizeMemberDisabled { + size_t size() const { return 42; } +}; + +// Intentionally disabling "const ConstSizeMemberDisabled". This doesn't disable anything +// because T is always uncvrefed before being checked. +template<> +inline constexpr bool std::disable_sized_range = true; + +struct SizeFunctionDisabled { + friend size_t size(SizeFunctionDisabled) { return 42; } +}; + +template<> +inline constexpr bool std::disable_sized_range = true; + +struct ConstSizeFunctionDisabled { + friend size_t size(const ConstSizeFunctionDisabled) { return 42; } +}; + +template<> +inline constexpr bool std::disable_sized_range = true; + +static_assert(!std::is_invocable_v); +static_assert(!std::is_invocable_v); +static_assert(!std::is_invocable_v); +static_assert(!std::is_invocable_v); + +// No begin end. +struct HasMinusOperator { + friend constexpr size_t operator-(HasMinusOperator, HasMinusOperator) { return 2; } +}; +static_assert(!std::is_invocable_v); + +struct sentinel { + bool operator==(std::input_or_output_iterator auto) const { return true; } +}; + +struct HasMinusBeginEnd { + friend constexpr forward_iterator begin(HasMinusBeginEnd) { return {}; } + friend constexpr sentinel end(HasMinusBeginEnd) { return {}; } +}; + +constexpr long operator-(const sentinel, const forward_iterator) { return 2; } +constexpr long operator-(const forward_iterator, const sentinel) { return 2; } + +struct other_forward_iterator : forward_iterator { }; + +struct InvalidMinusBeginEnd { + friend constexpr other_forward_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 int operator-(const sentinel, const other_forward_iterator) { return 2; } +constexpr int operator-(const other_forward_iterator, const sentinel) { return 2; } + +static_assert(!std::same_as); +static_assert(!std::is_invocable_v); + +struct BidirectionalRange { + constexpr random_access_iterator begin() { return {}; } + constexpr sentinel end() { return {}; } +}; + +constexpr long operator-(const sentinel, const random_access_iterator) { return 2; } +constexpr long operator-(const random_access_iterator, const sentinel) { return 2; } + +struct IntPtrBeginAndEnd { + int buff[8]; + constexpr int* begin() { return buff; } + constexpr int* end() { return buff + 8; } +}; + +struct DisabledSizeRangeWithBeginEnd { + int buff[8]; + constexpr int* begin() { return buff; } + constexpr int* end() { return buff + 8; } + constexpr size_t size() { return 1; } +}; + +template<> +inline constexpr bool std::disable_sized_range = true; + +constexpr bool testRanges() { + HasMinusBeginEnd a; + assert(std::ranges::size(a) == 2); + // Ensure that this is converted to an *unsigned* type. + ASSERT_SAME_TYPE(decltype(std::ranges::size(a)), unsigned long); + + IntPtrBeginAndEnd b; + assert(std::ranges::size(b) == 8); + + DisabledSizeRangeWithBeginEnd c; + assert(std::ranges::size(c) == 8); + + BidirectionalRange d; + assert(std::ranges::size(d) == 2); + ASSERT_SAME_TYPE(decltype(std::ranges::size(d)), unsigned long); + + return true; +} + +int main(int, char**) { + testArrayType(); + static_assert(testArrayType()); + + testHasSizeMember(); + static_assert(testHasSizeMember()); + + testHasSizeFunction(); + static_assert(testHasSizeFunction()); + + testRanges(); + static_assert(testRanges()); + + return 0; +}