Am Do., 12. Juni 2025 um 00:10 Uhr schrieb Jonathan Wakely < jwak...@redhat.com>:
> As suggested by Jason, this makes all __normal_iterator operators into > friends so they can be found by ADL and don't need to be separately > exported in module std. > > The operator<=> comparing two iterators of the same type is removed > entirely, instead of being made a hidden friend. That overload was added > by r12-5882-g2c7fb16b5283cf to deal with unconstrained operator > overloads found by ADL, as defined in the testsuite_greedy_ops.h header. > We don't actually test that case as there's no unconstrained <=> in that > header, and it doesn't seem reasonable for anybody to define such an > operator<=> in C++20 when they should constrain their overloads properly > (e.g. using a requires-clause). The heterogeneous operator<=> overloads > added for reverse_iterator and move_iterator could also be removed, but > that's not part of this commit. > > I also had to reorder the __attribute__((always_inline)) and > [[nodiscard]] attributes, which have to be in a particular order when > used on friend functions. > > libstdc++-v3/ChangeLog: > > * include/bits/stl_iterator.h (__normal_iterator): Make all > non-member operators hidden friends, except ... > (operator<=>(__normal_iterator<I,C>, __normal_iterator<I,C>)): > Remove. > * src/c++11/string-inst.cc: Remove explicit instantiations of > operators that are no longer templates. > * src/c++23/std.cc.in (__gnu_cxx): Do not export operators for > __normal_iterator. > --- > > v2: removed the unnecessary operator<=>, removed std.cc exports, fixed > other minor issues noticed by Patrick. > > Tested x86_64-linux. > > libstdc++-v3/include/bits/stl_iterator.h | 327 ++++++++++++----------- > libstdc++-v3/src/c++11/string-inst.cc | 11 - > libstdc++-v3/src/c++23/std.cc.in | 9 - > 3 files changed, 169 insertions(+), 178 deletions(-) > > diff --git a/libstdc++-v3/include/bits/stl_iterator.h > b/libstdc++-v3/include/bits/stl_iterator.h > index 478a98fe8a4f..a7188f46f6db 100644 > --- a/libstdc++-v3/include/bits/stl_iterator.h > +++ b/libstdc++-v3/include/bits/stl_iterator.h > @@ -1164,188 +1164,199 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > const _Iterator& > base() const _GLIBCXX_NOEXCEPT > { return _M_current; } > - }; > > - // Note: In what follows, the left- and right-hand-side iterators are > - // allowed to vary in types (conceptually in cv-qualification) so that > - // comparison between cv-qualified and non-cv-qualified iterators be > - // valid. However, the greedy and unfriendly operators in std::rel_ops > - // will make overload resolution ambiguous (when in scope) if we don't > - // provide overloads whose operands are of the same type. Can someone > - // remind me what generic programming is about? -- Gaby > + private: > + // Note: In what follows, the left- and right-hand-side iterators > are > + // allowed to vary in types (conceptually in cv-qualification) so > that > + // comparison between cv-qualified and non-cv-qualified iterators be > + // valid. However, the greedy and unfriendly operators in > std::rel_ops > + // will make overload resolution ambiguous (when in scope) if we > don't > + // provide overloads whose operands are of the same type. Can > someone > + // remind me what generic programming is about? -- Gaby > > #ifdef __cpp_lib_three_way_comparison > - template<typename _IteratorL, typename _IteratorR, typename _Container> > - [[nodiscard, __gnu__::__always_inline__]] > - constexpr bool > - operator==(const __normal_iterator<_IteratorL, _Container>& __lhs, > - const __normal_iterator<_IteratorR, _Container>& __rhs) > - noexcept(noexcept(__lhs.base() == __rhs.base())) > - requires requires { > - { __lhs.base() == __rhs.base() } -> std::convertible_to<bool>; > - } > - { return __lhs.base() == __rhs.base(); } > + template<typename _Iter> > + [[nodiscard, __gnu__::__always_inline__]] > + friend > + constexpr bool > + operator==(const __normal_iterator& __lhs, > + const __normal_iterator<_Iter, _Container>& __rhs) > + noexcept(noexcept(__lhs.base() == __rhs.base())) > + requires requires { > + { __lhs.base() == __rhs.base() } -> std::convertible_to<bool>; > + } > + { return __lhs.base() == __rhs.base(); } > > - template<typename _IteratorL, typename _IteratorR, typename _Container> > - [[nodiscard, __gnu__::__always_inline__]] > - constexpr std::__detail::__synth3way_t<_IteratorR, _IteratorL> > - operator<=>(const __normal_iterator<_IteratorL, _Container>& __lhs, > - const __normal_iterator<_IteratorR, _Container>& __rhs) > - noexcept(noexcept(std::__detail::__synth3way(__lhs.base(), > __rhs.base()))) > - { return std::__detail::__synth3way(__lhs.base(), __rhs.base()); } > + [[nodiscard, __gnu__::__always_inline__]] > + friend > + constexpr bool > + operator==(const __normal_iterator& __lhs, const __normal_iterator& > __rhs) > + noexcept(noexcept(__lhs.base() == __rhs.base())) > + requires requires { > + { __lhs.base() == __rhs.base() } -> std::convertible_to<bool>; > + } > + { return __lhs.base() == __rhs.base(); } > > - template<typename _Iterator, typename _Container> > - [[nodiscard, __gnu__::__always_inline__]] > - constexpr bool > - operator==(const __normal_iterator<_Iterator, _Container>& __lhs, > - const __normal_iterator<_Iterator, _Container>& __rhs) > - noexcept(noexcept(__lhs.base() == __rhs.base())) > - requires requires { > - { __lhs.base() == __rhs.base() } -> std::convertible_to<bool>; > - } > - { return __lhs.base() == __rhs.base(); } > - > - template<typename _Iterator, typename _Container> > - [[nodiscard, __gnu__::__always_inline__]] > - constexpr std::__detail::__synth3way_t<_Iterator> > - operator<=>(const __normal_iterator<_Iterator, _Container>& __lhs, > - const __normal_iterator<_Iterator, _Container>& __rhs) > - noexcept(noexcept(std::__detail::__synth3way(__lhs.base(), > __rhs.base()))) > - { return std::__detail::__synth3way(__lhs.base(), __rhs.base()); } > + template<typename _Iter> > + [[nodiscard, __gnu__::__always_inline__]] > + friend > + constexpr std::__detail::__synth3way_t<_Iterator, _Iter> > + operator<=>(const __normal_iterator& __lhs, > + const __normal_iterator<_Iter, _Container>& __rhs) > + noexcept(noexcept(std::__detail::__synth3way(__lhs.base(), > __rhs.base()))) > + requires requires { > + std::__detail::__synth3way(__lhs.base(), __rhs.base()); > + } > + { return std::__detail::__synth3way(__lhs.base(), __rhs.base()); } > #else > - // Forward iterator requirements > - template<typename _IteratorL, typename _IteratorR, typename _Container> > - _GLIBCXX_NODISCARD __attribute__((__always_inline__)) > _GLIBCXX_CONSTEXPR > - inline bool > - operator==(const __normal_iterator<_IteratorL, _Container>& __lhs, > - const __normal_iterator<_IteratorR, _Container>& __rhs) > - _GLIBCXX_NOEXCEPT > - { return __lhs.base() == __rhs.base(); } > + // Forward iterator requirements > + template<typename _Iter> > + __attribute__((__always_inline__)) _GLIBCXX_NODISCARD > Just curious: So in this case the attribute order intentionally potentially deviates from the order of the other friend functions using [[nodiscard, __gnu__::__always_inline__]]? - Daniel