On Mon, 2 Dec 2024 at 17:42, Patrick Palka <ppa...@redhat.com> 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.




> > > +      }
> > > +      { 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.

Reply via email to