[PATCH v2] libstdc++: Use hidden friends for __normal_iterator operators
Jonathan Wakely
jwakely@redhat.com
Thu Jun 12 08:21:23 GMT 2025
On Thu, 12 Jun 2025 at 08:12, Daniel Krügler <daniel.kruegler@gmail.com> wrote:
>
> Am Do., 12. Juni 2025 um 00:10 Uhr schrieb Jonathan Wakely <jwakely@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]]
| ^~~~~~~~~~~~~
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.
More information about the Libstdc++
mailing list