1 | //===----------------------------------------------------------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | // UNSUPPORTED: c++03, c++11, c++14, c++17 |
10 | |
11 | // template<class I> |
12 | // unspecified iter_move; |
13 | |
14 | #include <algorithm> |
15 | #include <array> |
16 | #include <cassert> |
17 | #include <iterator> |
18 | #include <type_traits> |
19 | #include <utility> |
20 | |
21 | #include "../unqualified_lookup_wrapper.h" |
22 | |
23 | using IterMoveT = decltype(std::ranges::iter_move); |
24 | |
25 | // Wrapper around an iterator for testing `iter_move` when an unqualified call to `iter_move` isn't |
26 | // possible. |
27 | template <typename I> |
28 | class iterator_wrapper { |
29 | public: |
30 | iterator_wrapper() = default; |
31 | |
32 | constexpr explicit iterator_wrapper(I i) noexcept : base_(std::move(i)) {} |
33 | |
34 | // `noexcept(false)` is used to check that this operator is called. |
35 | constexpr decltype(auto) operator*() const& noexcept(false) { return *base_; } |
36 | |
37 | // `noexcept` is used to check that this operator is called. |
38 | constexpr auto&& operator*() && noexcept { return std::move(*base_); } |
39 | |
40 | constexpr iterator_wrapper& operator++() noexcept { |
41 | ++base_; |
42 | return *this; |
43 | } |
44 | |
45 | constexpr void operator++(int) noexcept { ++base_; } |
46 | |
47 | constexpr bool operator==(iterator_wrapper const& other) const noexcept { return base_ == other.base_; } |
48 | |
49 | private: |
50 | I base_ = I{}; |
51 | }; |
52 | |
53 | template <class I> |
54 | iterator_wrapper(I) -> iterator_wrapper<I>; |
55 | |
56 | template <typename It, typename Out> |
57 | constexpr void unqualified_lookup_move(It first_, It last_, Out result_first_, Out result_last_) { |
58 | auto first = ::check_unqualified_lookup::unqualified_lookup_wrapper{std::move(first_)}; |
59 | auto last = ::check_unqualified_lookup::unqualified_lookup_wrapper{std::move(last_)}; |
60 | auto result_first = ::check_unqualified_lookup::unqualified_lookup_wrapper{std::move(result_first_)}; |
61 | auto result_last = ::check_unqualified_lookup::unqualified_lookup_wrapper{std::move(result_last_)}; |
62 | |
63 | static_assert(!noexcept(std::ranges::iter_move(first)), "unqualified-lookup case not being chosen" ); |
64 | |
65 | for (; first != last && result_first != result_last; (void)++first, ++result_first) { |
66 | *result_first = std::ranges::iter_move(first); |
67 | } |
68 | } |
69 | |
70 | template <typename It, typename Out> |
71 | constexpr void lvalue_move(It first_, It last_, Out result_first_, Out result_last_) { |
72 | auto first = iterator_wrapper{std::move(first_)}; |
73 | auto last = ::iterator_wrapper{std::move(last_)}; |
74 | auto result_first = iterator_wrapper{std::move(result_first_)}; |
75 | auto result_last = iterator_wrapper{std::move(result_last_)}; |
76 | |
77 | static_assert(!noexcept(std::ranges::iter_move(first)), "`operator*() const&` is not noexcept, and there's no hidden " |
78 | "friend iter_move." ); |
79 | |
80 | for (; first != last && result_first != result_last; (void)++first, ++result_first) { |
81 | *result_first = std::ranges::iter_move(first); |
82 | } |
83 | } |
84 | |
85 | template <typename It, typename Out> |
86 | constexpr void rvalue_move(It first_, It last_, Out result_first_, Out result_last_) { |
87 | auto first = iterator_wrapper{std::move(first_)}; |
88 | auto last = iterator_wrapper{std::move(last_)}; |
89 | auto result_first = iterator_wrapper{std::move(result_first_)}; |
90 | auto result_last = iterator_wrapper{std::move(result_last_)}; |
91 | |
92 | static_assert(noexcept(std::ranges::iter_move(std::move(first))), |
93 | "`operator*() &&` is noexcept, and there's no hidden friend iter_move." ); |
94 | |
95 | for (; first != last && result_first != result_last; (void)++first, ++result_first) { |
96 | auto i = first; |
97 | *result_first = std::ranges::iter_move(std::move(i)); |
98 | } |
99 | } |
100 | |
101 | template <bool NoExcept> |
102 | struct WithADL { |
103 | WithADL() = default; |
104 | constexpr int operator*() const { return 0; } |
105 | constexpr WithADL& operator++(); |
106 | constexpr void operator++(int); |
107 | constexpr bool operator==(WithADL const&) const; |
108 | friend constexpr int iter_move(WithADL&&) noexcept(NoExcept) { return 0; } |
109 | }; |
110 | |
111 | template <bool NoExcept> |
112 | struct WithoutADL { |
113 | WithoutADL() = default; |
114 | constexpr int operator*() const noexcept(NoExcept) { return 0; } |
115 | constexpr WithoutADL& operator++(); |
116 | constexpr void operator++(int); |
117 | constexpr bool operator==(WithoutADL const&) const; |
118 | }; |
119 | |
120 | constexpr bool test() { |
121 | constexpr int full_size = 100; |
122 | constexpr int half_size = full_size / 2; |
123 | constexpr int reset = 0; |
124 | auto v1 = std::array<move_tracker, full_size>{}; |
125 | |
126 | auto move_counter_is = [](auto const n) { return [n](auto const& x) { return x.moves() == n; }; }; |
127 | |
128 | auto v2 = std::array<move_tracker, half_size>{}; |
129 | unqualified_lookup_move(first_: v1.begin(), last_: v1.end(), result_first_: v2.begin(), result_last_: v2.end()); |
130 | assert(std::all_of(v1.cbegin(), v1.cend(), move_counter_is(reset))); |
131 | assert(std::all_of(v2.cbegin(), v2.cend(), move_counter_is(1))); |
132 | |
133 | auto v3 = std::array<move_tracker, half_size>{}; |
134 | unqualified_lookup_move(v1.begin() + half_size, v1.end(), v3.begin(), v3.end()); |
135 | assert(std::all_of(v1.cbegin(), v1.cend(), move_counter_is(reset))); |
136 | assert(std::all_of(v3.cbegin(), v3.cend(), move_counter_is(1))); |
137 | |
138 | auto v4 = std::array<move_tracker, half_size>{}; |
139 | unqualified_lookup_move(v3.begin(), v3.end(), v4.begin(), v4.end()); |
140 | assert(std::all_of(v3.cbegin(), v3.cend(), move_counter_is(reset))); |
141 | assert(std::all_of(v4.cbegin(), v4.cend(), move_counter_is(2))); |
142 | |
143 | lvalue_move(first_: v2.begin(), last_: v2.end(), result_first_: v1.begin() + half_size, result_last_: v1.end()); |
144 | assert(std::all_of(v2.cbegin(), v2.cend(), move_counter_is(reset))); |
145 | assert(std::all_of(v1.cbegin() + half_size, v1.cend(), move_counter_is(2))); |
146 | |
147 | lvalue_move(v4.begin(), v4.end(), v1.begin(), v1.end()); |
148 | assert(std::all_of(v4.cbegin(), v4.cend(), move_counter_is(reset))); |
149 | assert(std::all_of(v1.cbegin(), v1.cbegin() + half_size, move_counter_is(3))); |
150 | |
151 | rvalue_move(first_: v1.begin(), last_: v1.end(), result_first_: v2.begin(), result_last_: v2.end()); |
152 | assert(std::all_of(v1.cbegin(), v1.cbegin() + half_size, move_counter_is(reset))); |
153 | assert(std::all_of(v2.cbegin(), v2.cend(), move_counter_is(4))); |
154 | |
155 | rvalue_move(v1.begin() + half_size, v1.end(), v3.begin(), v3.end()); |
156 | assert(std::all_of(v1.cbegin(), v1.cend(), move_counter_is(reset))); |
157 | assert(std::all_of(v3.cbegin(), v3.cend(), move_counter_is(3))); |
158 | |
159 | auto unscoped = check_unqualified_lookup::unscoped_enum::a; |
160 | assert(std::ranges::iter_move(unscoped) == check_unqualified_lookup::unscoped_enum::a); |
161 | static_assert(!noexcept(std::ranges::iter_move(unscoped))); |
162 | |
163 | auto scoped = check_unqualified_lookup::scoped_enum::a; |
164 | assert(std::ranges::iter_move(scoped) == nullptr); |
165 | static_assert(noexcept(std::ranges::iter_move(scoped))); |
166 | |
167 | auto some_union = check_unqualified_lookup::some_union{.x: 0}; |
168 | assert(std::ranges::iter_move(some_union) == 0); |
169 | static_assert(!noexcept(std::ranges::iter_move(some_union))); |
170 | |
171 | // Check noexcept-correctness |
172 | static_assert(noexcept(std::ranges::iter_move(std::declval<WithADL<true>>()))); |
173 | static_assert(!noexcept(std::ranges::iter_move(std::declval<WithADL<false>>()))); |
174 | static_assert(noexcept(std::ranges::iter_move(std::declval<WithoutADL<true>>()))); |
175 | static_assert(!noexcept(std::ranges::iter_move(std::declval<WithoutADL<false>>()))); |
176 | |
177 | return true; |
178 | } |
179 | |
180 | static_assert(!std::is_invocable_v<IterMoveT, int*, int*>); // too many arguments |
181 | static_assert(!std::is_invocable_v<IterMoveT, int>); |
182 | |
183 | // Test ADL-proofing. |
184 | struct Incomplete; |
185 | template<class T> struct Holder { T t; }; |
186 | static_assert(std::is_invocable_v<IterMoveT, Holder<Incomplete>**>); |
187 | static_assert(std::is_invocable_v<IterMoveT, Holder<Incomplete>**&>); |
188 | |
189 | int main(int, char**) |
190 | { |
191 | test(); |
192 | static_assert(test()); |
193 | |
194 | return 0; |
195 | } |
196 | |