https://gcc.gnu.org/bugzilla/show_bug.cgi?id=112490
Barry Revzin <barry.revzin at gmail dot com> changed: What |Removed |Added ---------------------------------------------------------------------------- CC| |barry.revzin at gmail dot com --- Comment #3 from Barry Revzin <barry.revzin at gmail dot com> --- I ran into this also in a different direction, have been trying to reduce: #include <compare> template <class T, class U> concept my_partially_ordered_with = requires (T t, U u) { t < u; }; template <class T> concept my_totally_ordered = my_partially_ordered_with<T, T>; template <class T, class U> concept my_totally_ordered_with = my_totally_ordered<T> && my_totally_ordered<U> && my_partially_ordered_with<T, U>; template<class _It> class basic_const_iterator; namespace __detail { template<typename _Tp> inline constexpr bool __is_const_iterator = false; template<typename _It> inline constexpr bool __is_const_iterator<basic_const_iterator<_It>> = true; template<typename _Tp> concept __not_a_const_iterator = !__is_const_iterator<_Tp>; } // namespace detail template<class _It> class basic_const_iterator { #ifdef MAKE_THIS_PUBLIC public: #endif int m; public: template <__detail::__not_a_const_iterator _It2> friend bool operator<(const _It2& __x, const basic_const_iterator& __y) requires my_totally_ordered_with<_It, _It2> { return true; } }; template <class Iter> struct wrapped { Iter iter; constexpr std::strong_ordering operator<=>(const wrapped& rhs) const noexcept; }; bool check(wrapped<basic_const_iterator<int*>> x) { return x < x; } gcc rejects this with constraint recursion. clang and MSVC accept. Oddly, if you make _M_current public, gcc trunk accepts (even though no code even references this) while gcc 13.2 still rejects. On compiler explorer: https://godbolt.org/z/zfvxdGneK Trunk's rejection message is also incomplete: <source>: In substitution of 'template<class _It2> requires __not_a_const_iterator<_It2> bool operator<(const _It2&, const basic_const_iterator<int*>&) requires my_totally_ordered_with<_It, _It2> [with _It2 = int*]': <source>:3:89: required by substitution of 'template<class _It2> requires __not_a_const_iterator<_It2> bool operator<(const _It2&, const basic_const_iterator<int*>&) requires my_totally_ordered_with<_It, _It2> [with _It2 = int*]' 3 | template <class T, class U> concept my_partially_ordered_with = requires (T t, U u) { t < u; }; | ~~^~~ <source>:43:16: required from here 43 | return x < x; | ^ It tells you _It2=int*, but not what _It is. In this case, we're comparing two objects of type wrapped<basic_const_iterator<int*>> with <, so we should have two candidates: 1. wrapped's <=>, which has no constraints 2. basic_const_iterator<int*>'s friend operator<, which we start instantiating with _It=int* and _It2=wrapped<basic_const_iterator<int*>>. (2) requires checking if _It and _It2 can be ordered, which I think recursively instantiates itself. Assuming that's correct (hopefully?), I think CWG 2369 should actually cause this to be accepted - since wrapped<basic_const_iterator<int*>> is not convertible to basic_const_iterator<int*>, that should cause (2) to be rejected before we even consider constraints since it's not viable. I have no idea what the public-ness of the member changes.