On Mon, 2 Dec 2024, Jonathan Wakely wrote:
> On Mon, 2 Dec 2024 at 17:42, Patrick Palka <[email protected]> wrote:
> >
> > On Mon, 2 Dec 2024, Patrick Palka wrote:
> >
> > > On Thu, 28 Nov 2024, Jonathan Wakely wrote:
> > >
> > > > 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.
> > >
> > > Might as well remove the __gnu_cxx exports in std.cc.in while we're at
> > > it?
>
> Ah yes, since that was the original purpose of the change!
>
> > > >
> > > > For the operator<=> comparing two iterators of the same type, I had to
> > > > use a deduced return type and add a requires-clause, because it's no
> > > > longer a template and so we no longer get substitution failures when
> > > > it's considered in oerload resolution.
> > > >
> > > > 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.
> > > > * src/c++11/string-inst.cc: Remove explicit instantiations of
> > > > operators that are no longer templates.
> > > > ---
> > > >
> > > > Tested x86_64-linux.
> > > >
> > > > This iterator type isn't defined in the standard, and users shouldn't be
> > > > doing funny things with it, so nothing prevents us from replacing its
> > > > operators with hidden friends.
> > > >
> > > > libstdc++-v3/include/bits/stl_iterator.h | 341 ++++++++++++-----------
> > > > libstdc++-v3/src/c++11/string-inst.cc | 11 -
> > > > 2 files changed, 184 insertions(+), 168 deletions(-)
> > > >
> > > > diff --git a/libstdc++-v3/include/bits/stl_iterator.h
> > > > b/libstdc++-v3/include/bits/stl_iterator.h
> > > > index e872598d7d8..656a47e5f76 100644
> > > > --- a/libstdc++-v3/include/bits/stl_iterator.h
> > > > +++ b/libstdc++-v3/include/bits/stl_iterator.h
> > > > @@ -1164,188 +1164,215 @@ _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()); }
> > > > + template<typename _Iter>
> > > > + static constexpr bool __nothrow_synth3way
> > > > + = noexcept(std::__detail::__synth3way(std::declval<_Iterator&>(),
> > > > + std::declval<_Iter&>()));
> > >
> > > Since base() returns a const reference do we want to consider const
> > > references in this noexcept helper?
>
> Good catch.
>
> > >
> > > >
> > > > - 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 _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(__nothrow_synth3way<_Iter>)
> > > > + requires requires {
> > > > + std::__detail::__synth3way(__lhs.base(), __rhs.base());
> > > > + }
> > > > + { return std::__detail::__synth3way(__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()); }
> > > > + [[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(); }
> > > > +
> > > > + [[nodiscard, __gnu__::__always_inline__]]
> > > > + friend
> > > > + constexpr auto
> > > > + operator<=>(const __normal_iterator& __lhs,
> > > > + const __normal_iterator& __rhs)
> > > > + noexcept(__nothrow_synth3way<_Iterator>)
> > > > + requires requires {
> > > > + std::__detail::__synth3way(__lhs.base() == __rhs.base());
> > >
> > > Copy/paste typo here? s/==/,
>
> Fixed, thanks.
>
> > That this typo didn't cause a testsuite failure maybe suggests
> > this more specialized overload is redundant? Could we remove it and
> > rely only on the heterogenous operator<=> overload? (Then we could
> > inline the __nothrow_synth3way helper.)
>
> It does seem to be redundant. I tried removing the homogeneous
> operator== but that causes:
>
> FAIL: 24_iterators/normal_iterator/greedy_ops.cc -std=gnu++20 (test
> for excess errors)
>
> That testcase is arguably unreasonable, but it's what we've been
> testing and "supporting" for decades.
>
> If I make these changes to the testsuite then it fails without the
> homogeneous operator<=>:
>
> --- a/libstdc++-v3/testsuite/24_iterators/normal_iterator/greedy_ops.cc
> +++ b/libstdc++-v3/testsuite/24_iterators/normal_iterator/greedy_ops.cc
> @@ -37,6 +37,9 @@ void test01()
> iterator_type it(0);
>
> it == it;
> +#ifdef __cpp_lib_three_way_comparison
> + it <=> it;
> +#endif
> it != it;
> it < it;
> it <= it;
> --- a/libstdc++-v3/testsuite/util/testsuite_greedy_ops.h
> +++ b/libstdc++-v3/testsuite/util/testsuite_greedy_ops.h
> @@ -28,6 +28,12 @@ namespace greedy_ops
> X operator!=(T, T)
> { return X(); }
>
> +#ifdef __cpp_lib_three_way_comparison
> + template<typename T>
> + X operator<=>(T, T)
> + { return X(); }
> +#endif
> +
> template<typename T>
> X operator<(T, T)
> { return X(); }
>
> But I think it's OK to say that nobody should write that nonsense in
> C++20, because they can use constraints to write sensible operators
> that don't break things.
I wouldn't be against that. We'd effectively be partially reverting
r12-5882-g2c7fb16b5283cf which reintroduced homogenous operator==/<=>
for move_iterator and reverse_iterator as well.
If we decide to keep the homogenous operator<=> for these iterator
adaptors then we should define one for counted_iterator as well
since it currently only has heterogenous operator<=>/== and so is
susceptible to this ambiguity issue it seems.
>
>
>
>
> > > > + }
> > > > + { 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
> > > > + friend
> > > > + _GLIBCXX_CONSTEXPR
> > > > + inline bool
> > >
> > > IIUC hidden friends are implicitly inline so we can remove the inline
> > > keyword here.
>
> Yes, done here and for the ones below, thanks.
>
>