This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of C++23 status.
basic_const_iterator::operator== causes infinite constraint recursionSection: 24.5.3 [const.iterators] Status: C++23 Submitter: Hewill Kang Opened: 2022-09-05 Last modified: 2023-11-22
Priority: 1
View other active issues in [const.iterators].
View all other issues in [const.iterators].
View all issues with C++23 status.
Discussion:
Currently, basic_const_iterator::operator== is defined as a friend function:
template<sentinel_for<Iterator> S> friend constexpr bool operator==(const basic_const_iterator& x, const S& s);
which only requires S to model sentinel_for<Iterator>, and since
basic_const_iterator has a conversion constructor that accepts I, this will
result in infinite constraint checks when comparing basic_const_iterator<int*>
with int* (online example):
#include <iterator>
template<std::input_iterator I>
struct basic_const_iterator {
basic_const_iterator() = default;
basic_const_iterator(I);
template<std::sentinel_for<I> S>
friend bool operator==(const basic_const_iterator&, const S&);
};
static_assert(std::sentinel_for<basic_const_iterator<int*>, int*>); // infinite meta-recursion
That is, sentinel_for ends with weakly-equality-comparable-with
and instantiates operator==, which in turn rechecks sentinel_for and
instantiates the same operator==, making the circle closed.
operator== to be a member function so that
S is no longer accidentally instantiated as basic_const_iterator.
The same goes for basic_const_iterator::operator-.
[2022-09-23; Reflector poll]
Set priority to 1 after reflector poll.
"Although I am not a big fan of member ==, the proposed solution seems to be simple."
"prefer if we would keep operator== as non-member for consistency."
This wording is relative to N4917.
Modify 24.5.3.3 [const.iterators.iterator], class template
basic_const_iteratorsynopsis, as indicated:namespace std { template<class I> concept not-a-const-iterator = see below; template<input_iterator Iterator> class basic_const_iterator { Iterator current_ = Iterator(); using reference = iter_const_reference_t<Iterator>; // exposition only public: […] template<sentinel_for<Iterator> S>friendconstexpr bool operator==(const basic_const_iterator& x,const S& s) const; […] template<sized_sentinel_for<Iterator> S>friendconstexpr difference_type operator-(const basic_const_iterator& x,const S& y) const; template<not-a-const-iteratorsized_sentinel_for<Iterator>S> requires sized_sentinel_fordifferent-from<S, Iteratorbasic_const_iterator> friend constexpr difference_type operator-(const S& x, const basic_const_iterator& y); }; }Modify 24.5.3.5 [const.iterators.ops] as indicated:
[…]
template<sentinel_for<Iterator> S>friendconstexpr bool operator==(const basic_const_iterator& x,const S& s) const;[…]-16- Effects: Equivalent to:
return.x.current_ == s;template<sized_sentinel_for<Iterator> S>friendconstexpr difference_type operator-(const basic_const_iterator& x,const S& y) const;-24- Effects: Equivalent to:
return.x.current_ - y;template<not-a-const-iteratorsized_sentinel_for<Iterator>S> requires sized_sentinel_fordifferent-from<S, Iteratorbasic_const_iterator> friend constexpr difference_type operator-(const S& x, const basic_const_iterator& y);-25- Effects: Equivalent to:
return x - y.current_;.
[2022-11-04; Tomasz comments and improves proposed wording]
Initially, LWG requested an investigation of alternative resolutions that would avoid using member functions for the affected operators.
Later, it was found that in addition to ==/-, all comparison operators (<, >, <=, >=, <=>) are affected by same problem for the calls
with basic_const_iterator<basic_const_iterator<int*>> and int* as arguments, i.e. totally_ordered_with<basic_const_iterator<basic_const_iterator<int*>>, int*>
causes infinite recursion in constraint checking.
The new resolution, change all of the friends overloads for operators ==, <, >, <=, >=, <=> and - that accept basic_const_iterator as lhs, to const member functions.
This change is applied to homogeneous (basic_const_iterator, basic_const_iterator) for consistency.
For the overload of <, >, <=, >= and - that accepts (I, basic_const_iterator) we declared them as friends and consistently constrain them with not-const-iterator.
Finally, its put (now member) operator in the block with other heterogeneous overloads in the synopsis.
<=>(I)
The use of member functions addresses issues, because:
basic_const_iterator in the left-hand side of op, i.e. eliminates issues for (sized_)sentinel_for<basic_const_iterator<int*>, int*> and totally_ordered<basic_const_iterator<int*>, int*>basic_const_iterator<basic_const_iterator<S>>, so we address recursion for nested iterators[Kona 2022-11-08; Move to Ready]
[2023-02-13 Approved at February 2023 meeting in Issaquah. Status changed: Voting → WP.]
Proposed resolution:
This wording is relative to N4917.
Modify 24.5.3.3 [const.iterators.iterator], class template basic_const_iterator synopsis, as indicated:
namespace std {
template<class I>
concept not-a-const-iterator = see below;
template<input_iterator Iterator>
class basic_const_iterator {
Iterator current_ = Iterator();
using reference = iter_const_reference_t<Iterator>; // exposition only
public:
[…]
template<sentinel_for<Iterator> S>
friend constexpr bool operator==(const basic_const_iterator& x, const S& s) const;
friend constexpr bool operator<(const basic_const_iterator& x, const basic_const_iterator& y) const
requires random_access_iterator<Iterator>;
friend constexpr bool operator>(const basic_const_iterator& x, const basic_const_iterator& y) const
requires random_access_iterator<Iterator>;
friend constexpr bool operator<=(const basic_const_iterator& x, const basic_const_iterator& y) const
requires random_access_iterator<Iterator>;
friend constexpr bool operator>=(const basic_const_iterator& x, const basic_const_iterator& y) const
requires random_access_iterator<Iterator>;
friend constexpr auto operator<=>(const basic_const_iterator& x, const basic_const_iterator& y) const
requires random_access_iterator<Iterator> && three_way_comparable<Iterator>;
template<different-from<basic_const_iterator> I>
friend constexpr bool operator<(const basic_const_iterator& x, const I& y) const
requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I>;
template<different-from<basic_const_iterator> I>
friend constexpr bool operator>(const basic_const_iterator& x, const I& y) const
requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I>;
template<different-from<basic_const_iterator> I>
friend constexpr bool operator<=(const basic_const_iterator& x, const I& y) const
requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I>;
template<different-from<basic_const_iterator> I>
friend constexpr bool operator>=(const basic_const_iterator& x, const I& y) const
requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I>;
template<different-from<basic_const_iterator> I>
constexpr auto operator<=>(const I& y) const
requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I> &&
three_way_comparable_with<Iterator, I>;
template<not-a-const-iterator I>
friend constexpr bool operator<(const I& y, const basic_const_iterator& x)
requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I>;
template<not-a-const-iterator I>
friend constexpr bool operator>(const I& y, const basic_const_iterator& x)
requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I>;
template<not-a-const-iterator I>
friend constexpr bool operator<=(const I& y, const basic_const_iterator& x)
requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I>;
template<not-a-const-iterator I>
friend constexpr bool operator>=(const I& y, const basic_const_iterator& x)
requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I>;
template<different-from<basic_const_iterator> I>
friend constexpr auto operator<=>(const basic_const_iterator& x, const I& y)
requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I> &&
three_way_comparable_with<Iterator, I>;
[…]
template<sized_sentinel_for<Iterator> S>
friend constexpr difference_type operator-(const basic_const_iterator& x, const S& y) const;
template<not-a-const-iteratorsized_sentinel_for<Iterator> S>
requires sized_sentinel_fordifferent-from<S, Iteratorbasic_const_iterator>
friend constexpr difference_type operator-(const S& x, const basic_const_iterator& y);
};
}
Modify 24.5.3.5 [const.iterators.ops] as indicated:
[…]
template<sentinel_for<Iterator> S>friendconstexpr bool operator==(const basic_const_iterator& x,const S& s) const;-16- Effects: Equivalent to:
returnx.current_ == s;friendconstexpr bool operator<(const basic_const_iterator& x,const basic_const_iterator& y) const requires random_access_iterator<Iterator>;friendconstexpr bool operator>(const basic_const_iterator& x,const basic_const_iterator& y) const requires random_access_iterator<Iterator>;friendconstexpr bool operator<=(const basic_const_iterator& x,const basic_const_iterator& y) const requires random_access_iterator<Iterator>;friendconstexpr bool operator>=(const basic_const_iterator& x,const basic_const_iterator& y) const requires random_access_iterator<Iterator>;friendconstexpr auto operator<=>(const basic_const_iterator& x,const basic_const_iterator& y) const requires random_access_iterator<Iterator> && three_way_comparable<Iterator>;-17- Let op be the operator.
-18- Effects: Equivalent to:
returnx.current_ op y.current_;template<different-from<basic_const_iterator> I>friendconstexpr bool operator<(const basic_const_iterator& x,const I& y) const requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I>; template<different-from<basic_const_iterator> I>friendconstexpr bool operator>(const basic_const_iterator& x,const I& y) const requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I>; template<different-from<basic_const_iterator> I>friendconstexpr bool operator<=(const basic_const_iterator& x,const I& y) const requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I>; template<different-from<basic_const_iterator> I>friendconstexpr bool operator>=(const basic_const_iterator& x,const I& y) const requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I>; template<different-from<basic_const_iterator> I>friendconstexpr auto operator<=>(const basic_const_iterator& x,const I& y) const requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I> && three_way_comparable_with<Iterator, I>;[…]-19- Let
opbe the operator.-20-
ReturnsEffects: Equivalent to:returnx.current_ op y;template<sized_sentinel_for<Iterator> S>friendconstexpr difference_type operator-(const basic_const_iterator& x,const S& y) const;-24- Effects: Equivalent to:
returnx.current_ - y;template<not-a-const-iteratorsized_sentinel_for<Iterator>S> requires sized_sentinel_fordifferent-from<S, Iteratorbasic_const_iterator> friend constexpr difference_type operator-(const S& x, const basic_const_iterator& y);-25- Effects: Equivalent to:
return x - y.current_;