Am Do., 12. Juni 2025 um 10:22 Uhr schrieb Jonathan Wakely <
jwak...@redhat.com>:

> On Thu, 12 Jun 2025 at 09:21, Jonathan Wakely <jwak...@redhat.com> wrote:
> >
> > On Thu, 12 Jun 2025 at 08:12, Daniel Krügler <daniel.krueg...@gmail.com>
> wrote:
> > >
> > > 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__]]?
> >
> > Yes because operator== needs to compile as C++98 so it uses
> > __attribute__((always_inline)) not [[gnu::always_inline]], and Clang
> > will only allow __attribute__((foo)) [[bar]] and not the other way
> > around:
> > https://godbolt.org/z/nGWss9YG3
> >
> > <source>:5:36: error: an attribute list cannot appear here
> >     5 |     __attribute__((always_inline)) [[nodiscard]]
> >       |                                    ^~~~~~~~~~~~~
>
> I'll update the commit msg to note that the order is needed for Clang,
> as GCC seems happy with either order.
>
> >
> > We could change [[nodiscard,gnu::always_inline]] to be in the opposite
> > order, but when either order works, I prefer to have the standard
> > attribute first and the non-standard one second.
>

That would be helpful - thanks,

- Daniel

Reply via email to