diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -38,6 +38,7 @@ __ranges/access.h __ranges/concepts.h __ranges/enable_borrowed_range.h + __ranges/size.h __split_buffer __sso_allocator __std_stream diff --git a/libcxx/include/__iterator/concepts.h b/libcxx/include/__iterator/concepts.h --- a/libcxx/include/__iterator/concepts.h +++ b/libcxx/include/__iterator/concepts.h @@ -139,6 +139,11 @@ // clang-format on +template +[[nodiscard]] constexpr auto __to_unsigned_like(_From __x) noexcept { + return static_cast>(__x); +} + #endif // !defined(_LIBCPP_HAS_NO_RANGES) _LIBCPP_END_NAMESPACE_STD 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,111 @@ +// -*- 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_SIZE_H +#define _LIBCPP___RANGES_SIZE_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) + +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()) } -> __integer_like; + }; + + template + concept __unqualified_size = + __size_enabled<_Tp> && + !__member_size<_Tp> && + __class_or_enum> && + requires(_Tp&& __t) { + { _VSTD::__decay_copy(size(_VSTD::forward<_Tp>(__t))) } -> __integer_like; + }; + + 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 __integer_like auto operator()(_Tp&& __t) const + noexcept(noexcept(_VSTD::forward<_Tp>(__t).size())) { + return _VSTD::forward<_Tp>(__t).size(); + } + + template <__unqualified_size _Tp> + [[nodiscard]] constexpr __integer_like auto operator()(_Tp&& __t) const + noexcept(noexcept(size(_VSTD::forward<_Tp>(__t)))) { + return size(_VSTD::forward<_Tp>(__t)); + } + + template<__difference _Tp> + [[nodiscard]] constexpr __integer_like auto operator()(_Tp&& __t) const + noexcept(noexcept(ranges::end(__t) - ranges::begin(__t))) { + return __to_unsigned_like>>( + ranges::end(__t) - ranges::begin(__t)); + } + }; +} // end namespace __size + +inline namespace __cpo { + inline constexpr 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 @@ -67,6 +67,7 @@ #include <__ranges/access.h> #include <__ranges/concepts.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,295 @@ +//===----------------------------------------------------------------------===// +// +// 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" + +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::semiregular>); + +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); +}; + +struct BoolReturnTypeMember { + bool size() const; +}; + +struct BoolReturnTypeFunction { + friend bool size(BoolReturnTypeFunction const&); +}; + +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 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 short operator-(const sentinel, const other_forward_iterator) { return 2; } +constexpr short operator-(const other_forward_iterator, const sentinel) { return 2; } + +static_assert(!std::same_as); +static_assert(!std::is_invocable_v); + +struct RandomAccesslRange { + 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); + + RandomAccesslRange 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; +}