diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -19,6 +19,7 @@ __iterator/default_sentinel.h __iterator/incrementable_traits.h __iterator/iter_move.h + __iterator/iter_swap.h __iterator/iterator_traits.h __iterator/next.h __iterator/prev.h diff --git a/libcxx/include/__iterator/iter_swap.h b/libcxx/include/__iterator/iter_swap.h new file mode 100644 --- /dev/null +++ b/libcxx/include/__iterator/iter_swap.h @@ -0,0 +1,91 @@ +// -*- 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_SWAP_H +#define _LIBCPP___ITERATOR_ITER_SWAP_H + +#include <__config> +#include <__iterator/concepts.h> +#include <__iterator/iterator_traits.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) + +namespace ranges { +namespace __iter_swap { + template + void iter_swap(_I1, _I2) = delete; + + template + concept __unqualified_iter_swap = requires(_T1&& __x, _T2&& __y) { + iter_swap(_VSTD::forward<_T1>(__x), _VSTD::forward<_T2>(__y)); + }; + + template + concept __readable_swappable = + indirectly_readable<_T1> && indirectly_readable<_T2> && + swappable_with, iter_reference_t<_T2>>; + + struct __fn { + template + requires __unqualified_iter_swap<_T1, _T2> + constexpr void operator()(_T1&& __x, _T2&& __y) const + noexcept(noexcept(iter_swap(_VSTD::forward<_T1>(__x), _VSTD::forward<_T2>(__y)))) + { + (void)iter_swap(_VSTD::forward<_T1>(__x), _VSTD::forward<_T2>(__y)); + } + + template + requires (!__unqualified_iter_swap<_T1, _T2>) && + __readable_swappable<_T1, _T2> + constexpr void operator()(_T1&& __x, _T2&& __y) const + noexcept(noexcept(ranges::swap(*_VSTD::forward<_T1>(__x), *_VSTD::forward<_T2>(__y)))) + { + ranges::swap(*_VSTD::forward<_T1>(__x), *_VSTD::forward<_T2>(__y)); + } + + template + requires (!__unqualified_iter_swap<_T1, _T2> && + !__readable_swappable<_T1, _T2>) && + indirectly_movable_storable<_T1, _T2> && + indirectly_movable_storable<_T2, _T1> + constexpr void operator()(_T1&& __x, _T2&& __y) const + noexcept(noexcept(iter_value_t<_T2>(ranges::iter_move(__y))) && + noexcept(*__y = ranges::iter_move(__x)) && + noexcept(*_VSTD::forward<_T1>(__x) = declval>())) + { + iter_value_t<_T2> __old(ranges::iter_move(__y)); + *__y = ranges::iter_move(__x); + *_VSTD::forward<_T1>(__x) = _VSTD::move(__old); + } + }; +} // end namespace __iter_swap + +inline namespace __cpo { + inline constexpr auto iter_swap = __iter_swap::__fn{}; +} // namespace __cpo + +} // namespace ranges + +#endif // !defined(_LIBCPP_HAS_NO_RANGES) + +_LIBCPP_END_NAMESPACE_STD + +_LIBCPP_POP_MACROS + +#endif // _LIBCPP___ITERATOR_ITER_SWAP_H diff --git a/libcxx/include/iterator b/libcxx/include/iterator --- a/libcxx/include/iterator +++ b/libcxx/include/iterator @@ -563,6 +563,7 @@ #include <__iterator/default_sentinel.h> #include <__iterator/incrementable_traits.h> #include <__iterator/iter_move.h> +#include <__iterator/iter_swap.h> #include <__iterator/iterator_traits.h> #include <__iterator/next.h> #include <__iterator/prev.h> 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 --- 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 @@ -50,27 +50,6 @@ 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_)}; diff --git a/libcxx/test/std/iterators/iterator.requirements/iterator.cust/iterator.cust.swap.pass.cpp b/libcxx/test/std/iterators/iterator.requirements/iterator.cust/iterator.cust.swap.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/iterators/iterator.requirements/iterator.cust/iterator.cust.swap.pass.cpp @@ -0,0 +1,209 @@ +//===----------------------------------------------------------------------===// +// +// 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_swap; + +#include + +#include +#include + +#include "./unqualified_lookup_wrapper.h" +#include "test_iterators.h" + +using IterSwapT = decltype(std::ranges::iter_swap); + +static_assert(std::semiregular>); + +struct HasIterSwap { + int &value; + explicit HasIterSwap(int &value) : value(value) { assert(value == 0); } + + friend constexpr void iter_swap(HasIterSwap& a, HasIterSwap& b) { + a.value = 1; + b.value = 1; + } + friend constexpr void iter_swap(HasIterSwap& a, int& b) { + a.value = 2; + b = 2; + } +}; + +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); +static_assert(!std::is_invocable_v); + +struct NodiscardIterSwap { + [[nodiscard]] friend int iter_swap(NodiscardIterSwap&, NodiscardIterSwap&) { return 0; } +}; + +void ensureVoidCast(NodiscardIterSwap& a, NodiscardIterSwap& b) { std::ranges::iter_swap(a, b); } + +struct HasRangesSwap { + int &value; + explicit HasRangesSwap(int &value) : value(value) { assert(value == 0); } + + friend constexpr void swap(HasRangesSwap& a, HasRangesSwap& b) { + a.value = 1; + b.value = 1; + } + friend constexpr void swap(HasRangesSwap& a, int& b) { + a.value = 2; + b = 2; + } +}; + +struct HasRangesSwapWrapper { + using value_type = HasRangesSwap; + + HasRangesSwap &value; + explicit HasRangesSwapWrapper(HasRangesSwap &value) : value(value) {} + + HasRangesSwap& operator*() const { return value; } +}; + +static_assert( std::is_invocable_v); +// Does not satisfy swappable_with, even though swap(X, Y) is valid. +static_assert(!std::is_invocable_v); +static_assert(!std::is_invocable_v); + +struct B; + +struct A { + bool value = false; + A& operator=(const B&) { + value = true; + return *this; + }; +}; + +struct B { + bool value = false; + B& operator=(const A&) { + value = true; + return *this; + }; +}; + +struct MoveOnly2; + +struct MoveOnly1 { + bool value = false; + + MoveOnly1() = default; + MoveOnly1(MoveOnly1&&) = default; + MoveOnly1& operator=(MoveOnly1&&) = default; + MoveOnly1(const MoveOnly1&) = delete; + MoveOnly1& operator=(const MoveOnly1&) = delete; + + MoveOnly1& operator=(MoveOnly2 &&) { + value = true; + return *this; + }; +}; + +struct MoveOnly2 { + bool value = false; + + MoveOnly2() = default; + MoveOnly2(MoveOnly2&&) = default; + MoveOnly2& operator=(MoveOnly2&&) = default; + MoveOnly2(const MoveOnly2&) = delete; + MoveOnly2& operator=(const MoveOnly2&) = delete; + + MoveOnly2& operator=(MoveOnly1 &&) { + value = true; + return *this; + }; +}; + +int main(int, char**) { + { + int value1 = 0; + int value2 = 0; + HasIterSwap a(value1), b(value2); + std::ranges::iter_swap(a, b); + assert(value1 == 1 && value2 == 1); + } + + { + int value1 = 0; + int value2 = 0; + HasRangesSwap c(value1), d(value2); + HasRangesSwapWrapper cWrapper(c), dWrapper(d); + std::ranges::iter_swap(cWrapper, dWrapper); + assert(value1 == 1 && value2 == 1); + } + + { + int value1 = 0; + int value2 = 0; + HasRangesSwap c(value1), d(value2); + std::ranges::iter_swap(HasRangesSwapWrapper(c), HasRangesSwapWrapper(d)); + assert(value1 == 1 && value2 == 1); + } + + { + A e; B f; + A *ePtr = &e; + B *fPtr = &f; + std::ranges::iter_swap(ePtr, fPtr); + assert(e.value && f.value); + } + + { + MoveOnly1 g; MoveOnly2 h; + std::ranges::iter_swap(&g, &h); + assert(g.value && h.value); + } + + { + auto arr = std::array(); + std::ranges::iter_swap(arr.begin(), arr.begin() + 1); + assert(arr[0].moves() == 1 && arr[1].moves() == 2); + } + + { + int buff[2] = {1, 2}; + std::ranges::iter_swap(buff + 0, buff + 1); + assert(buff[0] == 2 && buff[1] == 1); + + std::ranges::iter_swap(cpp20_input_iterator(buff), cpp20_input_iterator(buff + 1)); + assert(buff[0] == 1 && buff[1] == 2); + + std::ranges::iter_swap(cpp17_input_iterator(buff), cpp17_input_iterator(buff + 1)); + assert(buff[0] == 2 && buff[1] == 1); + + std::ranges::iter_swap(forward_iterator(buff), forward_iterator(buff + 1)); + assert(buff[0] == 1 && buff[1] == 2); + + std::ranges::iter_swap(bidirectional_iterator(buff), bidirectional_iterator(buff + 1)); + assert(buff[0] == 2 && buff[1] == 1); + + std::ranges::iter_swap(random_access_iterator(buff), random_access_iterator(buff + 1)); + assert(buff[0] == 1 && buff[1] == 2); + + std::ranges::iter_swap(contiguous_iterator(buff), contiguous_iterator(buff + 1)); + assert(buff[0] == 2 && buff[1] == 1); + } + + 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 --- 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 @@ -57,4 +57,23 @@ } // namespace check_unqualified_lookup +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; + } + + move_tracker(move_tracker const& other) = delete; + move_tracker& operator=(move_tracker const& other) = delete; + + constexpr int moves() const noexcept { return moves_; } + +private: + int moves_ = 0; +}; + #endif // LIBCPP_TEST_STD_ITERATOR_UNQUALIFIED_LOOKUP_WRAPPER diff --git a/libcxx/test/std/iterators/predef.iterators/move.iterators/move.iterator/iterator_concept_conformance.compile.pass.cpp b/libcxx/test/std/iterators/predef.iterators/move.iterators/move.iterator/iterator_concept_conformance.compile.pass.cpp --- a/libcxx/test/std/iterators/predef.iterators/move.iterators/move.iterator/iterator_concept_conformance.compile.pass.cpp +++ b/libcxx/test/std/iterators/predef.iterators/move.iterators/move.iterator/iterator_concept_conformance.compile.pass.cpp @@ -23,5 +23,5 @@ static_assert(std::incrementable); static_assert(std::sentinel_for); static_assert(std::sized_sentinel_for); -static_assert(std::indirectly_movable); -static_assert(std::indirectly_movable_storable); +static_assert(!std::indirectly_movable); +static_assert(!std::indirectly_movable_storable); diff --git a/libcxx/test/std/iterators/stream.iterators/istream.iterator/iterator_concept_conformance.compile.pass.cpp b/libcxx/test/std/iterators/stream.iterators/istream.iterator/iterator_concept_conformance.compile.pass.cpp --- a/libcxx/test/std/iterators/stream.iterators/istream.iterator/iterator_concept_conformance.compile.pass.cpp +++ b/libcxx/test/std/iterators/stream.iterators/istream.iterator/iterator_concept_conformance.compile.pass.cpp @@ -24,5 +24,5 @@ static_assert(std::sentinel_for); static_assert(!std::sized_sentinel_for); static_assert(std::input_iterator); -static_assert(std::indirectly_movable); -static_assert(std::indirectly_movable_storable); +static_assert(!std::indirectly_movable); +static_assert(!std::indirectly_movable_storable);