| //===----------------------------------------------------------------------===// |
| // |
| // 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 |
| |
| // template<class I> |
| // unspecified iter_move; |
| |
| #include <algorithm> |
| #include <array> |
| #include <cassert> |
| #include <iterator> |
| #include <type_traits> |
| #include <utility> |
| |
| #include "../unqualified_lookup_wrapper.h" |
| |
| using IterMoveT = decltype(std::ranges::iter_move); |
| |
| // Wrapper around an iterator for testing `iter_move` when an unqualified call to `iter_move` isn't |
| // possible. |
| template <typename I> |
| 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. |
| constexpr decltype(auto) operator*() const& noexcept(false) { return *base_; } |
| |
| // `noexcept` is used to check that this operator is called. |
| constexpr auto&& operator*() && noexcept { return std::move(*base_); } |
| |
| constexpr iterator_wrapper& operator++() noexcept { |
| ++base_; |
| return *this; |
| } |
| |
| constexpr void operator++(int) noexcept { ++base_; } |
| |
| constexpr bool operator==(iterator_wrapper const& other) const noexcept { return base_ == other.base_; } |
| |
| private: |
| I base_ = I{}; |
| }; |
| |
| template <class I> |
| iterator_wrapper(I) -> iterator_wrapper<I>; |
| |
| template <typename It, typename Out> |
| constexpr void unqualified_lookup_move(It first_, It last_, Out result_first_, Out 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 <typename It, typename Out> |
| constexpr void lvalue_move(It first_, It last_, Out result_first_, Out 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 <typename It, typename Out> |
| constexpr void rvalue_move(It first_, It last_, Out result_first_, Out 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 <bool NoExcept> |
| 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 <bool NoExcept> |
| 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 test() { |
| constexpr int full_size = 100; |
| constexpr int half_size = full_size / 2; |
| constexpr int reset = 0; |
| auto v1 = std::array<move_tracker, full_size>{}; |
| |
| auto move_counter_is = [](auto const n) { return [n](auto const& x) { return x.moves() == n; }; }; |
| |
| auto v2 = std::array<move_tracker, half_size>{}; |
| 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<move_tracker, half_size>{}; |
| 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<move_tracker, half_size>{}; |
| 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<WithADL<true>>()))); |
| static_assert(!noexcept(std::ranges::iter_move(std::declval<WithADL<false>>()))); |
| static_assert(noexcept(std::ranges::iter_move(std::declval<WithoutADL<true>>()))); |
| static_assert(!noexcept(std::ranges::iter_move(std::declval<WithoutADL<false>>()))); |
| |
| return true; |
| } |
| |
| static_assert(!std::is_invocable_v<IterMoveT, int*, int*>); // too many arguments |
| static_assert(!std::is_invocable_v<IterMoveT, int>); |
| |
| // Test ADL-proofing. |
| struct Incomplete; |
| template<class T> struct Holder { T t; }; |
| static_assert(std::is_invocable_v<IterMoveT, Holder<Incomplete>**>); |
| static_assert(std::is_invocable_v<IterMoveT, Holder<Incomplete>**&>); |
| |
| int main(int, char**) |
| { |
| test(); |
| static_assert(test()); |
| |
| return 0; |
| } |