diff --git a/libcxx/include/concepts b/libcxx/include/concepts --- a/libcxx/include/concepts +++ b/libcxx/include/concepts @@ -246,19 +246,23 @@ constructible_from<_Tp, const _Tp&> && convertible_to && constructible_from<_Tp, const _Tp> && convertible_to; +template +concept __class_or_enum = + is_class_v> || + is_union_v> || + is_enum_v>; + // [concept.swappable] namespace ranges::__swap { // Deleted to inhibit ADL template void swap(_Tp&, _Tp&) = delete; - template - concept __class_or_enum = is_class_v<_Tp> || is_union_v<_Tp> || is_enum_v<_Tp>; // [1] template concept __unqualified_swappable_with = - (__class_or_enum> || __class_or_enum>) && + (__class_or_enum<_Tp> || __class_or_enum<_Up>) && requires(_Tp&& __t, _Up&& __u) { swap(_VSTD::forward<_Tp>(__t), _VSTD::forward<_Up>(__u)); }; @@ -439,7 +443,18 @@ // [concept.strictweakorder] template concept strict_weak_order = relation<_Rp, _Tp, _Up>; -#endif //_LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_CONCEPTS) + +// Obscurely named so the Clang diagnostic engine doesn't suggest it by accident. +template +concept __core_reference = is_reference_v<_Tp>; + +template +concept __lvalue_reference = __core_reference<_Tp> && is_lvalue_reference_v<_Tp>; + +template +concept __rvalue_reference = __core_reference<_Tp> && !__lvalue_reference<_Tp>; + +#endif // _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_CONCEPTS) _LIBCPP_END_NAMESPACE_STD diff --git a/libcxx/include/iterator b/libcxx/include/iterator --- a/libcxx/include/iterator +++ b/libcxx/include/iterator @@ -35,6 +35,19 @@ template using iter_reference_t = decltype(*declval()); +namespace ranges { + inline namespace unspecified { + inline constexpr unspecified iter_move = unspecified; // since C++20 + } +} + +template + requires requires(T& t) { + { ranges::iter_move(t) } -> can-reference; + } +using iter_rvalue_reference_t + = decltype(ranges::iter_move(declval())); + template struct iterator @@ -2407,6 +2420,85 @@ return __old_size - __c.size(); } +#if !defined(_LIBCPP_HAS_NO_RANGES) + +namespace ranges::__iter_move { +void iter_move(); + +template +concept __unqualified_iter_move = + __class_or_enum<_Ip> && + requires(_Ip&& __i) { + iter_move(_VSTD::forward<_Ip>(__i)); + }; + +template +concept __lvalue_iter_move = + !__unqualified_iter_move<_Ip> && + __dereferenceable<_Ip> && + requires(_Ip&& __i) { + { *_VSTD::forward<_Ip>(__i) } -> __lvalue_reference; + }; + +template +concept __rvalue_iter_move = + !__unqualified_iter_move<_Ip> && + !__lvalue_iter_move<_Ip> && + requires(_Ip&& __i) { + *_VSTD::forward<_Ip>(__i); + }; + +#define _LIBCPP_NOEXCEPT_RETURN(...) \ + noexcept(noexcept(__VA_ARGS__)) \ + { return __VA_ARGS__; } + +// The name `ranges::iter_­move` denotes a customization point object ([customization.point.object]). +// The expression `ranges::iter_­move(E)` for a subexpression `E` is expression-equivalent to: +struct __fn { + // `iter_­move(E)`, if `E` has class or enumeration type and `iter_­move(E)` is a well-formed + // expression when treated as an unevaluated operand, with overload resolution performed in a + // context that does not include a declaration of `ranges::iter_­move` but does include the + // declaration + // ``` + // void iter_move(); + // ``` + template<__unqualified_iter_move _Ip> + constexpr decltype(auto) operator()(_Ip&& __i) const + _LIBCPP_NOEXCEPT_RETURN( + iter_move(std::forward<_Ip>(__i)) + ) + + // Otherwise, if the expression `*E` is well-formed: + // if `*E` is an lvalue, `std::move(*E)`; + template<__lvalue_iter_move _Ip> + constexpr auto&& operator()(_Ip&& __i) const + _LIBCPP_NOEXCEPT_RETURN( + std::move(*std::forward<_Ip>(__i)) + ) + + // otherwise, `*E`. + template<__rvalue_iter_move _Ip> + constexpr decltype(auto) operator()(_Ip&& __i) const + _LIBCPP_NOEXCEPT_RETURN( + *std::forward<_Ip>(__i) + ) + + // Otherwise, `ranges​::​iter_­move(E)` is ill-formed. + void operator()(auto&&) const = delete; +}; +} // namespace ranges::__iter_move + +namespace ranges::inline __cpo { + inline constexpr auto iter_move = __iter_move::__fn{}; +} + +template<__dereferenceable _Tp> +requires requires(_Tp& __t) { { ranges::iter_move(__t) } -> __referenceable; } +using iter_rvalue_reference_t = decltype(ranges::iter_move(declval<_Tp&>())); + +#undef _LIBCPP_NOEXCEPT_RETURN +#endif // !defined(_LIBCPP_HAS_NO_RANGES) + _LIBCPP_END_NAMESPACE_STD #endif // _LIBCPP_ITERATOR diff --git a/libcxx/test/std/iterators/iterator.requirements/iterator.cust/iterator.cust.move/iter_move.pass.cpp b/libcxx/test/std/iterators/iterator.requirements/iterator.cust/iterator.cust.move/iter_move.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/iterators/iterator.requirements/iterator.cust/iterator.cust.move/iter_move.pass.cpp @@ -0,0 +1,168 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// template +// unspecified iter_move; + +#include + +#include +#include +#include +#include + +#include "../unqualified_lookup_wrapper.h" + +// Wrapper around an iterator for testing `iter_move` when an unqualified call to `iter_move` isn't +// possible. +template +class iterator_wrapper { +public: + iterator_wrapper() = default; + + constexpr explicit iterator_wrapper(I i) noexcept : base_(std::move(i)) {} + + /// \note `noexcept(false)` is used to check that this operator is called. + /// + [[nodiscard]] constexpr decltype(auto) operator*() const& noexcept(false) { return *base_; } + + /// \note `noexcept` is used to check that this operator is called. + /// + [[nodiscard]] constexpr auto&& operator*() && noexcept { return std::move(*base_); } + + constexpr iterator_wrapper& operator++() noexcept { + ++base_; + return *this; + } + + constexpr void operator++(int) noexcept { ++base_; } + + [[nodiscard]] constexpr bool operator==(iterator_wrapper const& other) const noexcept { return base_ == other.base_; } + +private: + I base_ = I{}; +}; + +class move_tracker { +public: + move_tracker() = default; + + constexpr move_tracker(move_tracker&& other) noexcept : moves_{other.moves_ + 1} { other.moves_ = 0; } + + constexpr move_tracker& operator=(move_tracker&& other) noexcept { + moves_ = other.moves_ + 1; + other.moves_ = 0; + return *this; + } + + constexpr move_tracker(move_tracker const& other) = delete; + constexpr move_tracker& operator=(move_tracker const& other) = delete; + + [[nodiscard]] constexpr int moves() const noexcept { return moves_; } + +private: + int moves_ = 0; +}; + +template +constexpr void unqualified_lookup_move(I first_, I last_, I result_first_, I result_last_) { + auto first = unqualified_lookup_wrapper{std::move(first_)}; + auto last = unqualified_lookup_wrapper{std::move(last_)}; + auto result_first = unqualified_lookup_wrapper{std::move(result_first_)}; + auto result_last = unqualified_lookup_wrapper{std::move(result_last_)}; + + static_assert(!noexcept(std::ranges::iter_move(first)), "unqualified-lookup case not being chosen"); + + for (; first != last && result_first != result_last; (void)++first, ++result_first) { + *result_first = std::ranges::iter_move(first); + } +} + +template +constexpr void lvalue_move(I first_, I last_, I result_first_, I result_last_) { + auto first = iterator_wrapper{std::move(first_)}; + auto last = ::iterator_wrapper{std::move(last_)}; + auto result_first = iterator_wrapper{std::move(result_first_)}; + auto result_last = iterator_wrapper{std::move(result_last_)}; + + static_assert(!noexcept(std::ranges::iter_move(first)), "`operator*() const&` is not noexcept, and there's no hidden " + "friend iter_move."); + + for (; first != last && result_first != result_last; (void)++first, ++result_first) { + *result_first = std::ranges::iter_move(first); + } +} + +template +constexpr void rvalue_move(I first_, I last_, I result_first_, I result_last_) { + auto first = iterator_wrapper{std::move(first_)}; + auto last = iterator_wrapper{std::move(last_)}; + auto result_first = iterator_wrapper{std::move(result_first_)}; + auto result_last = iterator_wrapper{std::move(result_last_)}; + + static_assert(noexcept(std::ranges::iter_move(std::move(first))), + "`operator*() &&` is noexcept, and there's no hidden friend iter_move."); + + for (; first != last && result_first != result_last; (void)++first, ++result_first) { + auto i = first; + *result_first = std::ranges::iter_move(std::move(i)); + } +} + +constexpr bool check_iter_move() { + constexpr int full_size = 100; + constexpr int half_size = full_size / 2; + constexpr int reset = 0; + auto v1 = std::array{}; + + auto move_counter_is = [](auto const n) { return [n](auto const& x) { return x.moves() == n; }; }; + + auto v2 = std::array{}; + unqualified_lookup_move(v1.begin(), v1.end(), v2.begin(), v2.end()); + assert(std::all_of(v1.cbegin(), v1.cend(), move_counter_is(reset))); + assert(std::all_of(v2.cbegin(), v2.cend(), move_counter_is(1))); + + auto v3 = std::array{}; + unqualified_lookup_move(v1.begin() + half_size, v1.end(), v3.begin(), v3.end()); + assert(std::all_of(v1.cbegin(), v1.cend(), move_counter_is(reset))); + assert(std::all_of(v3.cbegin(), v3.cend(), move_counter_is(1))); + + auto v4 = std::array{}; + unqualified_lookup_move(v3.begin(), v3.end(), v4.begin(), v4.end()); + assert(std::all_of(v3.cbegin(), v3.cend(), move_counter_is(reset))); + assert(std::all_of(v4.cbegin(), v4.cend(), move_counter_is(2))); + + lvalue_move(v2.begin(), v2.end(), v1.begin() + half_size, v1.end()); + assert(std::all_of(v2.cbegin(), v2.cend(), move_counter_is(reset))); + assert(std::all_of(v1.cbegin() + half_size, v1.cend(), move_counter_is(2))); + + lvalue_move(v4.begin(), v4.end(), v1.begin(), v1.end()); + assert(std::all_of(v4.cbegin(), v4.cend(), move_counter_is(reset))); + assert(std::all_of(v1.cbegin(), v1.cbegin() + half_size, move_counter_is(3))); + + rvalue_move(v1.begin(), v1.end(), v2.begin(), v2.end()); + assert(std::all_of(v1.cbegin(), v1.cbegin() + half_size, move_counter_is(reset))); + assert(std::all_of(v2.cbegin(), v2.cend(), move_counter_is(4))); + + rvalue_move(v1.begin() + half_size, v1.end(), v3.begin(), v3.end()); + assert(std::all_of(v1.cbegin(), v1.cend(), move_counter_is(reset))); + assert(std::all_of(v3.cbegin(), v3.cend(), move_counter_is(3))); + + return true; +} + +int main(int, char**) { + static_assert(check_iter_move()); + assert(check_iter_move()); + + return 0; +} diff --git a/libcxx/test/std/iterators/iterator.requirements/iterator.cust/iterator.cust.move/iter_rvalue_reference_t.pass.cpp b/libcxx/test/std/iterators/iterator.requirements/iterator.cust/iterator.cust.move/iter_rvalue_reference_t.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/iterators/iterator.requirements/iterator.cust/iterator.cust.move/iter_rvalue_reference_t.pass.cpp @@ -0,0 +1,26 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// template +// using iter_rvalue_reference; + +#include + +#include +#include +#include + +static_assert(std::same_as::iterator&>, int&&>); +static_assert(std::same_as::const_iterator>, int const&&>); +static_assert(std::same_as::iterator>, int const&&>); + +int main(int, char**) { return 0; } diff --git a/libcxx/test/std/iterators/iterator.requirements/iterator.cust/unqualified_lookup_wrapper.h b/libcxx/test/std/iterators/iterator.requirements/iterator.cust/unqualified_lookup_wrapper.h new file mode 100644 --- /dev/null +++ b/libcxx/test/std/iterators/iterator.requirements/iterator.cust/unqualified_lookup_wrapper.h @@ -0,0 +1,45 @@ +//===----------------------------------------------------------------------===// +// +// 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_TEST_STD_ITERATOR_UNQUALIFIED_LOOKUP_WRAPPER +#define LIBCPP_TEST_STD_ITERATOR_UNQUALIFIED_LOOKUP_WRAPPER + +#include +#include + +// Wrapper around an iterator for testing unqualified calls to `iter_move` and `iter_swap`. +template +class unqualified_lookup_wrapper { +public: + unqualified_lookup_wrapper() = default; + + constexpr explicit unqualified_lookup_wrapper(I i) noexcept : base_(std::move(i)) {} + + [[nodiscard]] constexpr decltype(auto) operator*() const noexcept { return *base_; } + + constexpr unqualified_lookup_wrapper& operator++() noexcept { + ++base_; + return *this; + } + + constexpr void operator++(int) noexcept { ++base_; } + + [[nodiscard]] constexpr bool operator==(unqualified_lookup_wrapper const& other) const noexcept { + return base_ == other.base_; + } + + // Delegates `std::ranges::iter_move` for the underlying iterator. `noexcept(false)` will be used + // to ensure that the unqualified-lookup overload is chosen. + [[nodiscard]] friend constexpr decltype(auto) iter_move(unqualified_lookup_wrapper& i) noexcept(false) { + return std::ranges::iter_move(i.base_); + } + +private: + I base_ = I{}; +}; + +#endif // LIBCPP_TEST_STD_ITERATOR_UNQUALIFIED_LOOKUP_WRAPPER