basic_const_iterator's relational operators due to ADL + CWG 2369Section: 24.5.3.5 [const.iterators.ops] Status: New Submitter: Patrick Palka Opened: 2025-03-03 Last modified: 2025-06-13
Priority: 2
View all issues with New status.
Discussion:
Consider the example (devised by Hewill Kang)
using RCI = reverse_iterator<basic_const_iterator<vector<int>::iterator>>; static_assert(std::totally_ordered<RCI>);
Checking RCI is totally_ordered entails checking
requires (RCI x) { x RELOP x; } for each RELOP in {<, >, <=, >=}
which we expect to be straightforwardly satisfied by reverse_iterator's
namespace-scope operators (24.5.1.8 [reverse.iter.cmp]):
template<class Iterator1, class Iterator2>
constexpr bool operator<(
const reverse_iterator<Iterator1>& x,
const reverse_iterator<Iterator2>& y);
// etc
But due to ADL we find ourselves also considering the basic_const_iterator
relop friends (24.5.3.5 [const.iterators.ops]/24).
template<input_iterator Iterator>
class basic_const_iterator {
template<not-a-const-iterator I>
friend constexpr bool operator<(const I& x, const basic_const_iterator& y)
requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I>
// etc
};
Before CWG 2369 these candidates would quickly get
discarded since for the second operand RCI clearly isn't convertible to basic_const_iterator.
But after CWG 2369 implementations must first check these operators' constraints
(with Iterator = vector<int>::iterator and I = RCI), which entails
checking totally_ordered<RCI> recursively, causing the example to be ill-formed.
basic_const_iterator<J> where J is
constrained to match Iterator:
template<not-a-const-iterator I, same_as<Iterator> J>
friend constexpr bool operator<(const I& x, const basic_const_iterator<J>& y)
requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I>
// etc
So that deduction fails earlier, before constraints get checked, for a second
operand that isn't a specialization of basic_const_iterator (or derived from one).
basic_const_iterator's
operators, but there the recursion was independent of CWG 2369.
[2025-06-13; Reflector poll]
Set priority to 2 after reflector poll.
Proposed resolution:
This wording is relative to N5001.
Modify 24.5.3.3 [const.iterators.iterator], class template basic_const_iterator synopsis, as indicated:
namespace std {
[…]
template<input_iterator Iterator>
class basic_const_iterator {
[…]
template<not-a-const-iterator I, same_as<Iterator> J>
friend constexpr bool operator<(const I& x, const basic_const_iterator<J>& y)
requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I>;
template<not-a-const-iterator I, same_as<Iterator> J>
friend constexpr bool operator>(const I& x, const basic_const_iterator<J>& y)
requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I>;
template<not-a-const-iterator I, same_as<Iterator> J>
friend constexpr bool operator<=(const I& x, const basic_const_iterator<J>& y)
requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I>;
template<not-a-const-iterator I, same_as<Iterator> J>
friend constexpr bool operator>=(const I& x, const basic_const_iterator<J>& y)
requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I>;
[…]
};
}
Modify 24.5.3.5 [const.iterators.ops] as indicated:
template<not-a-const-iterator I, same_as<Iterator> J> friend constexpr bool operator<(const I& x, const basic_const_iterator<J>& y) requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I>; template<not-a-const-iterator I, same_as<Iterator> J> friend constexpr bool operator>(const I& x, const basic_const_iterator<J>& y) requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I>; template<not-a-const-iterator I, same_as<Iterator> J> friend constexpr bool operator<=(const I& x, const basic_const_iterator<J>& y) requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I>; template<not-a-const-iterator I, same_as<Iterator> J> friend constexpr bool operator>=(const I& x, const basic_const_iterator<J>& y) requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I>;-23- Let
-24- Effects: Equivalent to:opbe the operator.return x op y.current_;