diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -12,8 +12,9 @@ __functional_base_03 __hash_table __iterator/concepts.h - __iterator/iterator_traits.h __iterator/incrementable_traits.h + __iterator/iter_move.h + __iterator/iterator_traits.h __iterator/readable_traits.h __libcpp_version __locale diff --git a/libcxx/include/__iterator/iter_move.h b/libcxx/include/__iterator/iter_move.h new file mode 100644 --- /dev/null +++ b/libcxx/include/__iterator/iter_move.h @@ -0,0 +1,86 @@ +// -*- 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___ITERATOR_ITER_MOVE_H +#define _LIBCPP___ITERATOR_ITER_MOVE_H + +#include <__config> +#include <__iterator/concepts.h> // __class_or_enum +#include // __class_or_enum +#include +#include + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +#pragma GCC system_header +#endif + +_LIBCPP_PUSH_MACROS +#include <__undef_macros> + +_LIBCPP_BEGIN_NAMESPACE_STD + +namespace ranges::__iter_move { +void iter_move(); + +template +concept __unqualified_iter_move = requires(_Ip&& __i) { + iter_move(_VSTD::forward<_Ip>(__i)); +}; + +// [iterator.cust.move]/1 +// The name ranges::iter_move denotes a customization point object. +// The expression ranges::iter_move(E) for a subexpression E is +// expression-equivalent to: +struct __fn { + // [iterator.cust.move]/1.1 + // 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, [...] + template + requires __class_or_enum> && __unqualified_iter_move<_Ip> + [[nodiscard]] constexpr decltype(auto) operator()(_Ip&& __i) const + noexcept(noexcept(iter_move(_VSTD::forward<_Ip>(__i)))) + { + return iter_move(_VSTD::forward<_Ip>(__i)); + } + + // [iterator.cust.move]/1.2 + // Otherwise, if the expression *E is well-formed: + // 1.2.1 if *E is an lvalue, std::move(*E); + // 1.2.2 otherwise, *E. + template + requires (!(__class_or_enum> && __unqualified_iter_move<_Ip>)) && + requires(_Ip&& __i) { *_VSTD::forward<_Ip>(__i); } + [[nodiscard]] constexpr decltype(auto) operator()(_Ip&& __i) const + noexcept(noexcept(*_VSTD::forward<_Ip>(__i))) + { + if constexpr (is_lvalue_reference_v(__i))>) { + return _VSTD::move(*_VSTD::forward<_Ip>(__i)); + } else { + return *_VSTD::forward<_Ip>(__i); + } + } + + // [iterator.cust.move]/1.3 + // Otherwise, ranges::iter_move(E) is ill-formed. +}; +} // 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&>())); + +_LIBCPP_END_NAMESPACE_STD + +_LIBCPP_POP_MACROS + +#endif // _LIBCPP___ITERATOR_ITER_MOVE_H diff --git a/libcxx/include/concepts b/libcxx/include/concepts --- a/libcxx/include/concepts +++ b/libcxx/include/concepts @@ -246,14 +246,16 @@ constructible_from<_Tp, const _Tp&> && convertible_to && constructible_from<_Tp, const _Tp> && convertible_to; +// Whether a type is a class type or enumeration type according to the Core wording. +template +concept __class_or_enum = is_class_v<_Tp> || is_union_v<_Tp> || is_enum_v<_Tp>; + // [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 @@ -440,7 +442,7 @@ template concept strict_weak_order = relation<_Rp, _Tp, _Up>; -#endif //_LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_CONCEPTS) +#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,14 @@ template using iter_reference_t = decltype(*declval()); +namespace ranges::inline unspecified { + inline constexpr unspecified iter_move = unspecified; // since C++20, nodiscard as an extension +}} + +template + requires ... +using iter_rvalue_reference_t = decltype(ranges::iter_move(declval())); // since C++20 + template struct iterator @@ -428,6 +436,10 @@ #include <__memory/pointer_traits.h> #include +#if !defined(_LIBCPP_HAS_NO_RANGES) +# include <__iterator/iter_move.h> +#endif + #include <__debug> #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) diff --git a/libcxx/test/std/iterators/iterator.requirements/iterator.cust/iterator.cust.move/iter_move.nodiscard.verify.cpp b/libcxx/test/std/iterators/iterator.requirements/iterator.cust/iterator.cust.move/iter_move.nodiscard.verify.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/iterators/iterator.requirements/iterator.cust/iterator.cust.move/iter_move.nodiscard.verify.cpp @@ -0,0 +1,38 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// Test the [[nodiscard]] extension in libc++. +// REQUIRES: libc++ + +// template +// unspecified iter_move; + +#include + +struct WithADL { + WithADL() = default; + constexpr decltype(auto) operator*() const noexcept; + constexpr WithADL& operator++() noexcept; + constexpr void operator++(int) noexcept; + constexpr bool operator==(WithADL const&) const noexcept; + friend constexpr auto iter_move(WithADL&) { return 0; } +}; + +int main(int, char**) { + int* noADL = nullptr; + std::ranges::iter_move(noADL); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}} + + WithADL adl; + std::ranges::iter_move(adl); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}} + + return 0; +} 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,212 @@ +//===----------------------------------------------------------------------===// +// +// 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)) {} + + // `noexcept(false)` is used to check that this operator is called. + [[nodiscard]] constexpr decltype(auto) operator*() const& noexcept(false) { return *base_; } + + // `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 = ::check_unqualified_lookup::unqualified_lookup_wrapper{std::move(first_)}; + auto last = ::check_unqualified_lookup::unqualified_lookup_wrapper{std::move(last_)}; + auto result_first = ::check_unqualified_lookup::unqualified_lookup_wrapper{std::move(result_first_)}; + auto result_last = ::check_unqualified_lookup::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)); + } +} + +template +struct WithADL { + WithADL() = default; + constexpr int operator*() const { return 0; } + constexpr WithADL& operator++(); + constexpr void operator++(int); + constexpr bool operator==(WithADL const&) const; + friend constexpr int iter_move(WithADL&&) noexcept(NoExcept) { return 0; } +}; + +template +struct WithoutADL { + WithoutADL() = default; + constexpr int operator*() const noexcept(NoExcept) { return 0; } + constexpr WithoutADL& operator++(); + constexpr void operator++(int); + constexpr bool operator==(WithoutADL const&) const; +}; + +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))); + + auto unscoped = check_unqualified_lookup::unscoped_enum::a; + assert(std::ranges::iter_move(unscoped) == check_unqualified_lookup::unscoped_enum::a); + assert(!noexcept(std::ranges::iter_move(unscoped))); + + auto scoped = check_unqualified_lookup::scoped_enum::a; + assert(std::ranges::iter_move(scoped) == nullptr); + assert(noexcept(std::ranges::iter_move(scoped))); + + auto some_union = check_unqualified_lookup::some_union{0}; + assert(std::ranges::iter_move(some_union) == 0); + assert(!noexcept(std::ranges::iter_move(some_union))); + + // Check noexcept-correctness + static_assert(noexcept(std::ranges::iter_move(std::declval>()))); + static_assert(!noexcept(std::ranges::iter_move(std::declval>()))); + static_assert(noexcept(std::ranges::iter_move(std::declval>()))); + static_assert(!noexcept(std::ranges::iter_move(std::declval>()))); + + return true; +} + +template +concept can_iter_move = requires (T t) { std::ranges::iter_move(t); }; + +int main(int, char**) { + static_assert(check_iter_move()); + check_iter_move(); + + // Make sure that `iter_move` SFINAEs away when the type can't be iter_move'd + { + struct NoIterMove { }; + static_assert(!can_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,60 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +namespace check_unqualified_lookup { +// 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{}; +}; + +enum unscoped_enum { a, b, c }; +constexpr unscoped_enum iter_move(unscoped_enum& e) noexcept(false) { return e; } + +enum class scoped_enum { a, b, c }; +constexpr scoped_enum* iter_move(scoped_enum&) noexcept { return nullptr; } + +union some_union { + int x; + double y; +}; +constexpr int iter_move(some_union& u) noexcept(false) { return u.x; } + +} // namespace check_unqualified_lookup + +#endif // LIBCPP_TEST_STD_ITERATOR_UNQUALIFIED_LOOKUP_WRAPPER