Skip to content

Commit a8ee522

Browse files
committed
libstdc++: Fix ranges::iter_move handling of rvalues [PR106612]
The specification for std::ranges::iter_move apparently requires us to handle types which do not satisfy std::indirectly_readable, for example with overloaded operator* which behaves differently for different value categories. libstdc++-v3/ChangeLog: PR libstdc++/106612 * include/bits/iterator_concepts.h (_IterMove::__iter_ref_t): New alias template. (_IterMove::__result): Use __iter_ref_t instead of std::iter_reference_t. (_IterMove::__type): Remove incorrect __dereferenceable constraint. (_IterMove::operator()): Likewise. Add correct constraints. Use __iter_ref_t instead of std::iter_reference_t. Forward parameter as correct value category. (iter_swap): Add comments. * testsuite/24_iterators/customization_points/iter_move.cc: Test that iter_move is found by ADL and that rvalue arguments are handled correctly. Reviewed-by: Patrick Palka <[email protected]>
1 parent 3866ca7 commit a8ee522

File tree

2 files changed

+119
-9
lines changed

2 files changed

+119
-9
lines changed

libstdc++-v3/include/bits/iterator_concepts.h

+24-9
Original file line numberDiff line numberDiff line change
@@ -103,32 +103,42 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
103103
namespace ranges
104104
{
105105
/// @cond undocumented
106+
// Implementation of std::ranges::iter_move, [iterator.cust.move].
106107
namespace __imove
107108
{
108109
void iter_move() = delete;
109110

111+
// Satisfied if _Tp is a class or enumeration type and iter_move
112+
// can be found by argument-dependent lookup.
110113
template<typename _Tp>
111114
concept __adl_imove
112115
= (std::__detail::__class_or_enum<remove_reference_t<_Tp>>)
113-
&& requires(_Tp&& __t) { iter_move(static_cast<_Tp&&>(__t)); };
116+
&& requires(_Tp&& __t) { iter_move(static_cast<_Tp&&>(__t)); };
114117

115118
struct _IterMove
116119
{
117120
private:
121+
// The type returned by dereferencing a value of type _Tp.
122+
// Unlike iter_reference_t this preserves the value category of _Tp.
123+
template<typename _Tp>
124+
using __iter_ref_t = decltype(*std::declval<_Tp>());
125+
118126
template<typename _Tp>
119127
struct __result
120-
{ using type = iter_reference_t<_Tp>; };
128+
{ using type = __iter_ref_t<_Tp>; };
121129

130+
// Use iter_move(E) if that works.
122131
template<typename _Tp>
123132
requires __adl_imove<_Tp>
124133
struct __result<_Tp>
125134
{ using type = decltype(iter_move(std::declval<_Tp>())); };
126135

136+
// Otherwise, if *E if an lvalue, use std::move(*E).
127137
template<typename _Tp>
128138
requires (!__adl_imove<_Tp>)
129-
&& is_lvalue_reference_v<iter_reference_t<_Tp>>
139+
&& is_lvalue_reference_v<__iter_ref_t<_Tp>>
130140
struct __result<_Tp>
131-
{ using type = remove_reference_t<iter_reference_t<_Tp>>&&; };
141+
{ using type = remove_reference_t<__iter_ref_t<_Tp>>&&; };
132142

133143
template<typename _Tp>
134144
static constexpr bool
@@ -142,21 +152,22 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
142152

143153
public:
144154
// The result type of iter_move(std::declval<_Tp>())
145-
template<std::__detail::__dereferenceable _Tp>
155+
template<typename _Tp>
146156
using __type = typename __result<_Tp>::type;
147157

148-
template<std::__detail::__dereferenceable _Tp>
158+
template<typename _Tp>
159+
requires __adl_imove<_Tp> || requires { typename __iter_ref_t<_Tp>; }
149160
[[nodiscard]]
150161
constexpr __type<_Tp>
151162
operator()(_Tp&& __e) const
152163
noexcept(_S_noexcept<_Tp>())
153164
{
154165
if constexpr (__adl_imove<_Tp>)
155166
return iter_move(static_cast<_Tp&&>(__e));
156-
else if constexpr (is_lvalue_reference_v<iter_reference_t<_Tp>>)
157-
return static_cast<__type<_Tp>>(*__e);
167+
else if constexpr (is_lvalue_reference_v<__iter_ref_t<_Tp>>)
168+
return std::move(*static_cast<_Tp&&>(__e));
158169
else
159-
return *__e;
170+
return *static_cast<_Tp&&>(__e);
160171
}
161172
};
162173
} // namespace __imove
@@ -167,6 +178,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
167178
}
168179
} // namespace ranges
169180

181+
/// The result type of ranges::iter_move(std::declval<_Tp&>())
170182
template<__detail::__dereferenceable _Tp>
171183
requires __detail::__can_reference<ranges::__imove::_IterMove::__type<_Tp&>>
172184
using iter_rvalue_reference_t = ranges::__imove::_IterMove::__type<_Tp&>;
@@ -873,11 +885,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
873885
namespace ranges
874886
{
875887
/// @cond undocumented
888+
// Implementation of std::ranges::iter_swap, [iterator.cust.swap].
876889
namespace __iswap
877890
{
878891
template<typename _It1, typename _It2>
879892
void iter_swap(_It1, _It2) = delete;
880893

894+
// Satisfied if _Tp and _Up are class or enumeration types and iter_swap
895+
// can be found by argument-dependent lookup.
881896
template<typename _Tp, typename _Up>
882897
concept __adl_iswap
883898
= (std::__detail::__class_or_enum<remove_reference_t<_Tp>>

libstdc++-v3/testsuite/24_iterators/customization_points/iter_move.cc

+95
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,103 @@ test01()
6363
VERIFY( test_X(3, 4) );
6464
}
6565

66+
template<typename T>
67+
using rval_ref = std::iter_rvalue_reference_t<T>;
68+
69+
static_assert(std::same_as<rval_ref<int*>, int&&>);
70+
static_assert(std::same_as<rval_ref<const int*>, const int&&>);
71+
static_assert(std::same_as<rval_ref<std::move_iterator<int*>>, int&&>);
72+
73+
template<typename T>
74+
concept iter_movable = requires { std::ranges::iter_move(std::declval<T>()); };
75+
76+
struct Iter
77+
{
78+
friend int& iter_move(Iter&) { static int i = 1; return i; }
79+
friend long iter_move(Iter&&) { return 2; }
80+
const short& operator*() const & { static short s = 3; return s; }
81+
friend float operator*(const Iter&&) { return 4.0f; }
82+
};
83+
84+
void
85+
test_adl()
86+
{
87+
Iter it;
88+
const Iter& cit = it;
89+
90+
VERIFY( std::ranges::iter_move(it) == 1 );
91+
VERIFY( std::ranges::iter_move(std::move(it)) == 2 );
92+
VERIFY( std::ranges::iter_move(cit) == 3 );
93+
VERIFY( std::ranges::iter_move(std::move(cit)) == 4.0f );
94+
95+
// The return type should be unchanged for ADL iter_move:
96+
static_assert(std::same_as<decltype(std::ranges::iter_move(it)), int&>);
97+
static_assert(std::same_as<decltype(std::ranges::iter_move(std::move(it))),
98+
long>);
99+
// When ADL iter_move is not used, return type should be an rvalue:
100+
static_assert(std::same_as<decltype(std::ranges::iter_move(cit)),
101+
const short&&>);
102+
static_assert(std::same_as<decltype(std::ranges::iter_move(std::move(cit))),
103+
float>);
104+
105+
// std::iter_rvalue_reference_t always considers the argument as lvalue.
106+
static_assert(std::same_as<rval_ref<Iter>, int&>);
107+
static_assert(std::same_as<rval_ref<Iter&>, int&>);
108+
static_assert(std::same_as<rval_ref<const Iter>, const short&&>);
109+
static_assert(std::same_as<rval_ref<const Iter&>, const short&&>);
110+
}
111+
112+
void
113+
test_pr106612()
114+
{
115+
// Bug 106612 ranges::iter_move does not consider iterator's value categories
116+
117+
struct I
118+
{
119+
int i{};
120+
int& operator*() & { return i; }
121+
int operator*() const & { return i; }
122+
void operator*() && = delete;
123+
};
124+
125+
static_assert( iter_movable<I&> );
126+
static_assert( iter_movable<I const&> );
127+
static_assert( ! iter_movable<I> );
128+
static_assert( std::same_as<std::iter_rvalue_reference_t<I>, int&&> );
129+
static_assert( std::same_as<std::iter_rvalue_reference_t<const I>, int> );
130+
131+
struct I2
132+
{
133+
int i{};
134+
int& operator*() & { return i; }
135+
int operator*() const & { return i; }
136+
void operator*() &&;
137+
};
138+
139+
static_assert( iter_movable<I2&> );
140+
static_assert( iter_movable<I2 const&> );
141+
static_assert( iter_movable<I2> );
142+
static_assert( std::is_void_v<decltype(std::ranges::iter_move(I2{}))> );
143+
static_assert( std::same_as<std::iter_rvalue_reference_t<I2>, int&&> );
144+
static_assert( std::same_as<std::iter_rvalue_reference_t<I2 const>, int> );
145+
146+
enum E { e };
147+
enum F { f };
148+
149+
struct I3
150+
{
151+
E operator*() const & { return e; }
152+
F operator*() && { return f; }
153+
};
154+
155+
static_assert( iter_movable<I3&> );
156+
static_assert( iter_movable<I3> );
157+
static_assert( std::same_as<decltype(std::ranges::iter_move(I3{})), F> );
158+
}
159+
66160
int
67161
main()
68162
{
69163
test01();
164+
test_adl();
70165
}

0 commit comments

Comments
 (0)