libstdc++: Use hidden friends for __normal_iterator operators

Message ID 20241128185011.935096-1-jwakely@redhat.com
State New
Headers
Series libstdc++: Use hidden friends for __normal_iterator operators |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gcc_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_gcc_check--master-arm success Test passed
linaro-tcwg-bot/tcwg_gcc_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_gcc_check--master-aarch64 success Test passed

Commit Message

Jonathan Wakely Nov. 28, 2024, 6:49 p.m. UTC
  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.

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(-)
  

Comments

Patrick Palka Dec. 2, 2024, 4:31 p.m. UTC | #1
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?

> 
> 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?

>  
> -  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/==/,

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

> +	operator==(const __normal_iterator& __lhs,
> +		   const __normal_iterator<_Iter, _Container>& __rhs)
> +	_GLIBCXX_NOEXCEPT
> +	{ return __lhs.base() == __rhs.base(); }
>  
> -  template<typename _Iterator, typename _Container>
> -    _GLIBCXX_NODISCARD __attribute__((__always_inline__)) _GLIBCXX_CONSTEXPR
> -    inline bool
> -    operator==(const __normal_iterator<_Iterator, _Container>& __lhs,
> -	       const __normal_iterator<_Iterator, _Container>& __rhs)
> -    _GLIBCXX_NOEXCEPT
> -    { return __lhs.base() == __rhs.base(); }
> +      __attribute__((__always_inline__)) _GLIBCXX_NODISCARD
> +      friend
> +      _GLIBCXX_CONSTEXPR
> +      inline bool
> +      operator==(const __normal_iterator& __lhs, const __normal_iterator& __rhs)
> +      _GLIBCXX_NOEXCEPT
> +      { return __lhs.base() == __rhs.base(); }
>  
> -  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(); }
> +      template<typename _Iter>
> +	__attribute__((__always_inline__)) _GLIBCXX_NODISCARD
> +	friend
> +	_GLIBCXX_CONSTEXPR
> +	inline bool
> +	operator!=(const __normal_iterator& __lhs,
> +		   const __normal_iterator<_Iter, _Container>& __rhs)
> +	_GLIBCXX_NOEXCEPT
> +	{ return __lhs.base() != __rhs.base(); }
>  
> -  template<typename _Iterator, typename _Container>
> -    _GLIBCXX_NODISCARD __attribute__((__always_inline__)) _GLIBCXX_CONSTEXPR
> -    inline bool
> -    operator!=(const __normal_iterator<_Iterator, _Container>& __lhs,
> -	       const __normal_iterator<_Iterator, _Container>& __rhs)
> -    _GLIBCXX_NOEXCEPT
> -    { return __lhs.base() != __rhs.base(); }
> +      __attribute__((__always_inline__)) _GLIBCXX_NODISCARD
> +      friend
> +      _GLIBCXX_CONSTEXPR
> +      inline bool
> +      operator!=(const __normal_iterator& __lhs, const __normal_iterator& __rhs)
> +      _GLIBCXX_NOEXCEPT
> +      { return __lhs.base() != __rhs.base(); }
>  
> -  // Random access 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(); }
> +      // Random access iterator requirements
> +      template<typename _Iter>
> +	friend
> +	__attribute__((__always_inline__)) _GLIBCXX_NODISCARD _GLIBCXX_CONSTEXPR
> +	inline bool
> +	operator<(const __normal_iterator& __lhs,
> +		  const __normal_iterator<_Iter, _Container>& __rhs)
> +	_GLIBCXX_NOEXCEPT
> +	{ return __lhs.base() < __rhs.base(); }
>  
> -  template<typename _Iterator, typename _Container>
> -    _GLIBCXX_NODISCARD __attribute__((__always_inline__)) _GLIBCXX20_CONSTEXPR
> -    inline bool
> -    operator<(const __normal_iterator<_Iterator, _Container>& __lhs,
> -	      const __normal_iterator<_Iterator, _Container>& __rhs)
> -    _GLIBCXX_NOEXCEPT
> -    { return __lhs.base() < __rhs.base(); }
> +      __attribute__((__always_inline__)) _GLIBCXX_NODISCARD
> +      friend
> +      _GLIBCXX20_CONSTEXPR
> +      inline bool
> +      operator<(const __normal_iterator& __lhs, const __normal_iterator& __rhs)
> +      _GLIBCXX_NOEXCEPT
> +      { return __lhs.base() < __rhs.base(); }
>  
> -  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(); }
> +      template<typename _Iter>
> +	friend
> +	__attribute__((__always_inline__)) _GLIBCXX_NODISCARD _GLIBCXX_CONSTEXPR
> +	inline bool
> +	operator>(const __normal_iterator& __lhs,
> +		  const __normal_iterator<_Iter, _Container>& __rhs)
> +	_GLIBCXX_NOEXCEPT
> +	{ return __lhs.base() > __rhs.base(); }
>  
> -  template<typename _Iterator, typename _Container>
> -    _GLIBCXX_NODISCARD __attribute__((__always_inline__)) _GLIBCXX_CONSTEXPR
> -    inline bool
> -    operator>(const __normal_iterator<_Iterator, _Container>& __lhs,
> -	      const __normal_iterator<_Iterator, _Container>& __rhs)
> -    _GLIBCXX_NOEXCEPT
> -    { return __lhs.base() > __rhs.base(); }
> +      __attribute__((__always_inline__)) _GLIBCXX_NODISCARD
> +      friend
> +      _GLIBCXX_CONSTEXPR
> +      inline bool
> +      operator>(const __normal_iterator& __lhs, const __normal_iterator& __rhs)
> +      _GLIBCXX_NOEXCEPT
> +      { return __lhs.base() > __rhs.base(); }
>  
> -  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(); }
> +      template<typename _Iter>
> +	__attribute__((__always_inline__)) _GLIBCXX_NODISCARD
> +	friend
> +	_GLIBCXX_CONSTEXPR
> +	inline bool
> +	operator<=(const __normal_iterator& __lhs,
> +		   const __normal_iterator<_Iter, _Container>& __rhs)
> +	_GLIBCXX_NOEXCEPT
> +	{ return __lhs.base() <= __rhs.base(); }
>  
> -  template<typename _Iterator, typename _Container>
> -    _GLIBCXX_NODISCARD __attribute__((__always_inline__)) _GLIBCXX_CONSTEXPR
> -    inline bool
> -    operator<=(const __normal_iterator<_Iterator, _Container>& __lhs,
> -	       const __normal_iterator<_Iterator, _Container>& __rhs)
> -    _GLIBCXX_NOEXCEPT
> -    { return __lhs.base() <= __rhs.base(); }
> +      __attribute__((__always_inline__)) _GLIBCXX_NODISCARD
> +      friend
> +      _GLIBCXX_CONSTEXPR
> +      inline bool
> +      operator<=(const __normal_iterator& __lhs, const __normal_iterator& __rhs)
> +      _GLIBCXX_NOEXCEPT
> +      { return __lhs.base() <= __rhs.base(); }
>  
> -  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(); }
> +      template<typename _Iter>
> +	__attribute__((__always_inline__)) _GLIBCXX_NODISCARD
> +	friend
> +	_GLIBCXX_CONSTEXPR
> +	inline bool
> +	operator>=(const __normal_iterator& __lhs,
> +		   const __normal_iterator<_Iter, _Container>& __rhs)
> +	_GLIBCXX_NOEXCEPT
> +	{ return __lhs.base() >= __rhs.base(); }
>  
> -  template<typename _Iterator, typename _Container>
> -    _GLIBCXX_NODISCARD __attribute__((__always_inline__)) _GLIBCXX_CONSTEXPR
> -    inline bool
> -    operator>=(const __normal_iterator<_Iterator, _Container>& __lhs,
> -	       const __normal_iterator<_Iterator, _Container>& __rhs)
> -    _GLIBCXX_NOEXCEPT
> -    { return __lhs.base() >= __rhs.base(); }
> +      __attribute__((__always_inline__)) _GLIBCXX_NODISCARD
> +      friend
> +      _GLIBCXX_CONSTEXPR
> +      inline bool
> +      operator>=(const __normal_iterator& __lhs, const __normal_iterator& __rhs)
> +      _GLIBCXX_NOEXCEPT
> +      { return __lhs.base() >= __rhs.base(); }
>  #endif // three-way comparison
>  
> -  // _GLIBCXX_RESOLVE_LIB_DEFECTS
> -  // According to the resolution of DR179 not only the various comparison
> -  // operators but also operator- must accept mixed iterator/const_iterator
> -  // parameters.
> -  template<typename _IteratorL, typename _IteratorR, typename _Container>
> +      // _GLIBCXX_RESOLVE_LIB_DEFECTS
> +      // 179. Comparison of const_iterators to iterators doesn't work
> +      // According to the resolution of DR179 not only the various comparison
> +      // operators but also operator- must accept mixed iterator/const_iterator
> +      // parameters.
> +      template<typename _Iter>
>  #if __cplusplus >= 201103L
> -    // DR 685.
> -    [[__nodiscard__, __gnu__::__always_inline__]]
> -    constexpr auto
> -    operator-(const __normal_iterator<_IteratorL, _Container>& __lhs,
> -	      const __normal_iterator<_IteratorR, _Container>& __rhs) noexcept
> -    -> decltype(__lhs.base() - __rhs.base())
> +	[[__nodiscard__, __gnu__::__always_inline__]]
> +	friend
> +	// _GLIBCXX_RESOLVE_LIB_DEFECTS
> +	// 685. reverse_iterator/move_iterator difference has invalid signatures
> +	constexpr auto
> +	operator-(const __normal_iterator& __lhs,
> +		  const __normal_iterator<_Iter, _Container>& __rhs) noexcept
> +	-> decltype(__lhs.base() - __rhs.base())
>  #else
> -    inline typename __normal_iterator<_IteratorL, _Container>::difference_type
> -    operator-(const __normal_iterator<_IteratorL, _Container>& __lhs,
> -	      const __normal_iterator<_IteratorR, _Container>& __rhs)
> +	friend
> +	difference_type
> +	operator-(const __normal_iterator& __lhs,
> +		  const __normal_iterator<_Iter, _Container>& __rhs)
>  #endif
> -    { return __lhs.base() - __rhs.base(); }
> +	{ return __lhs.base() - __rhs.base(); }
>  
> -  template<typename _Iterator, typename _Container>
> -    _GLIBCXX_NODISCARD __attribute__((__always_inline__)) _GLIBCXX_CONSTEXPR
> -    inline typename __normal_iterator<_Iterator, _Container>::difference_type
> -    operator-(const __normal_iterator<_Iterator, _Container>& __lhs,
> -	      const __normal_iterator<_Iterator, _Container>& __rhs)
> -    _GLIBCXX_NOEXCEPT
> -    { return __lhs.base() - __rhs.base(); }
> +      __attribute__((__always_inline__)) _GLIBCXX_NODISCARD
> +      friend
> +      _GLIBCXX_CONSTEXPR
> +      inline difference_type
> +      operator-(const __normal_iterator& __lhs, const __normal_iterator& __rhs)
> +	_GLIBCXX_NOEXCEPT
> +      { return __lhs.base() - __rhs.base(); }
>  
> -  template<typename _Iterator, typename _Container>
> -    _GLIBCXX_NODISCARD __attribute__((__always_inline__)) _GLIBCXX_CONSTEXPR
> -    inline __normal_iterator<_Iterator, _Container>
> -    operator+(typename __normal_iterator<_Iterator, _Container>::difference_type
> -	      __n, const __normal_iterator<_Iterator, _Container>& __i)
> -    _GLIBCXX_NOEXCEPT
> -    { return __normal_iterator<_Iterator, _Container>(__i.base() + __n); }
> +      __attribute__((__always_inline__)) _GLIBCXX_NODISCARD
> +      friend
> +      _GLIBCXX_CONSTEXPR
> +      inline __normal_iterator
> +      operator+(difference_type __n, const __normal_iterator& __i)
> +	_GLIBCXX_NOEXCEPT
> +      { return __normal_iterator(__i.base() + __n); }
> +    };
>  
>  _GLIBCXX_END_NAMESPACE_VERSION
>  } // namespace __gnu_cxx
> diff --git a/libstdc++-v3/src/c++11/string-inst.cc b/libstdc++-v3/src/c++11/string-inst.cc
> index ec5ba41ebcd..b8806304949 100644
> --- a/libstdc++-v3/src/c++11/string-inst.cc
> +++ b/libstdc++-v3/src/c++11/string-inst.cc
> @@ -110,14 +110,3 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>  
>  _GLIBCXX_END_NAMESPACE_VERSION
>  } // namespace
> -
> -namespace __gnu_cxx _GLIBCXX_VISIBILITY(default)
> -{
> -_GLIBCXX_BEGIN_NAMESPACE_VERSION
> -
> -  using std::S;
> -  template bool operator==(const S::iterator&, const S::iterator&);
> -  template bool operator==(const S::const_iterator&, const S::const_iterator&);
> -
> -_GLIBCXX_END_NAMESPACE_VERSION
> -} // namespace
> -- 
> 2.47.0
> 
>
  
Patrick Palka Dec. 2, 2024, 5:42 p.m. UTC | #2
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?
> 
> > 
> > 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?
> 
> >  
> > -  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/==/,

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.)

> 
> > +      }
> > +      { 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.
> 
> > +	operator==(const __normal_iterator& __lhs,
> > +		   const __normal_iterator<_Iter, _Container>& __rhs)
> > +	_GLIBCXX_NOEXCEPT
> > +	{ return __lhs.base() == __rhs.base(); }
> >  
> > -  template<typename _Iterator, typename _Container>
> > -    _GLIBCXX_NODISCARD __attribute__((__always_inline__)) _GLIBCXX_CONSTEXPR
> > -    inline bool
> > -    operator==(const __normal_iterator<_Iterator, _Container>& __lhs,
> > -	       const __normal_iterator<_Iterator, _Container>& __rhs)
> > -    _GLIBCXX_NOEXCEPT
> > -    { return __lhs.base() == __rhs.base(); }
> > +      __attribute__((__always_inline__)) _GLIBCXX_NODISCARD
> > +      friend
> > +      _GLIBCXX_CONSTEXPR
> > +      inline bool
> > +      operator==(const __normal_iterator& __lhs, const __normal_iterator& __rhs)
> > +      _GLIBCXX_NOEXCEPT
> > +      { return __lhs.base() == __rhs.base(); }
> >  
> > -  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(); }
> > +      template<typename _Iter>
> > +	__attribute__((__always_inline__)) _GLIBCXX_NODISCARD
> > +	friend
> > +	_GLIBCXX_CONSTEXPR
> > +	inline bool
> > +	operator!=(const __normal_iterator& __lhs,
> > +		   const __normal_iterator<_Iter, _Container>& __rhs)
> > +	_GLIBCXX_NOEXCEPT
> > +	{ return __lhs.base() != __rhs.base(); }
> >  
> > -  template<typename _Iterator, typename _Container>
> > -    _GLIBCXX_NODISCARD __attribute__((__always_inline__)) _GLIBCXX_CONSTEXPR
> > -    inline bool
> > -    operator!=(const __normal_iterator<_Iterator, _Container>& __lhs,
> > -	       const __normal_iterator<_Iterator, _Container>& __rhs)
> > -    _GLIBCXX_NOEXCEPT
> > -    { return __lhs.base() != __rhs.base(); }
> > +      __attribute__((__always_inline__)) _GLIBCXX_NODISCARD
> > +      friend
> > +      _GLIBCXX_CONSTEXPR
> > +      inline bool
> > +      operator!=(const __normal_iterator& __lhs, const __normal_iterator& __rhs)
> > +      _GLIBCXX_NOEXCEPT
> > +      { return __lhs.base() != __rhs.base(); }
> >  
> > -  // Random access 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(); }
> > +      // Random access iterator requirements
> > +      template<typename _Iter>
> > +	friend
> > +	__attribute__((__always_inline__)) _GLIBCXX_NODISCARD _GLIBCXX_CONSTEXPR
> > +	inline bool
> > +	operator<(const __normal_iterator& __lhs,
> > +		  const __normal_iterator<_Iter, _Container>& __rhs)
> > +	_GLIBCXX_NOEXCEPT
> > +	{ return __lhs.base() < __rhs.base(); }
> >  
> > -  template<typename _Iterator, typename _Container>
> > -    _GLIBCXX_NODISCARD __attribute__((__always_inline__)) _GLIBCXX20_CONSTEXPR
> > -    inline bool
> > -    operator<(const __normal_iterator<_Iterator, _Container>& __lhs,
> > -	      const __normal_iterator<_Iterator, _Container>& __rhs)
> > -    _GLIBCXX_NOEXCEPT
> > -    { return __lhs.base() < __rhs.base(); }
> > +      __attribute__((__always_inline__)) _GLIBCXX_NODISCARD
> > +      friend
> > +      _GLIBCXX20_CONSTEXPR
> > +      inline bool
> > +      operator<(const __normal_iterator& __lhs, const __normal_iterator& __rhs)
> > +      _GLIBCXX_NOEXCEPT
> > +      { return __lhs.base() < __rhs.base(); }
> >  
> > -  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(); }
> > +      template<typename _Iter>
> > +	friend
> > +	__attribute__((__always_inline__)) _GLIBCXX_NODISCARD _GLIBCXX_CONSTEXPR
> > +	inline bool
> > +	operator>(const __normal_iterator& __lhs,
> > +		  const __normal_iterator<_Iter, _Container>& __rhs)
> > +	_GLIBCXX_NOEXCEPT
> > +	{ return __lhs.base() > __rhs.base(); }
> >  
> > -  template<typename _Iterator, typename _Container>
> > -    _GLIBCXX_NODISCARD __attribute__((__always_inline__)) _GLIBCXX_CONSTEXPR
> > -    inline bool
> > -    operator>(const __normal_iterator<_Iterator, _Container>& __lhs,
> > -	      const __normal_iterator<_Iterator, _Container>& __rhs)
> > -    _GLIBCXX_NOEXCEPT
> > -    { return __lhs.base() > __rhs.base(); }
> > +      __attribute__((__always_inline__)) _GLIBCXX_NODISCARD
> > +      friend
> > +      _GLIBCXX_CONSTEXPR
> > +      inline bool
> > +      operator>(const __normal_iterator& __lhs, const __normal_iterator& __rhs)
> > +      _GLIBCXX_NOEXCEPT
> > +      { return __lhs.base() > __rhs.base(); }
> >  
> > -  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(); }
> > +      template<typename _Iter>
> > +	__attribute__((__always_inline__)) _GLIBCXX_NODISCARD
> > +	friend
> > +	_GLIBCXX_CONSTEXPR
> > +	inline bool
> > +	operator<=(const __normal_iterator& __lhs,
> > +		   const __normal_iterator<_Iter, _Container>& __rhs)
> > +	_GLIBCXX_NOEXCEPT
> > +	{ return __lhs.base() <= __rhs.base(); }
> >  
> > -  template<typename _Iterator, typename _Container>
> > -    _GLIBCXX_NODISCARD __attribute__((__always_inline__)) _GLIBCXX_CONSTEXPR
> > -    inline bool
> > -    operator<=(const __normal_iterator<_Iterator, _Container>& __lhs,
> > -	       const __normal_iterator<_Iterator, _Container>& __rhs)
> > -    _GLIBCXX_NOEXCEPT
> > -    { return __lhs.base() <= __rhs.base(); }
> > +      __attribute__((__always_inline__)) _GLIBCXX_NODISCARD
> > +      friend
> > +      _GLIBCXX_CONSTEXPR
> > +      inline bool
> > +      operator<=(const __normal_iterator& __lhs, const __normal_iterator& __rhs)
> > +      _GLIBCXX_NOEXCEPT
> > +      { return __lhs.base() <= __rhs.base(); }
> >  
> > -  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(); }
> > +      template<typename _Iter>
> > +	__attribute__((__always_inline__)) _GLIBCXX_NODISCARD
> > +	friend
> > +	_GLIBCXX_CONSTEXPR
> > +	inline bool
> > +	operator>=(const __normal_iterator& __lhs,
> > +		   const __normal_iterator<_Iter, _Container>& __rhs)
> > +	_GLIBCXX_NOEXCEPT
> > +	{ return __lhs.base() >= __rhs.base(); }
> >  
> > -  template<typename _Iterator, typename _Container>
> > -    _GLIBCXX_NODISCARD __attribute__((__always_inline__)) _GLIBCXX_CONSTEXPR
> > -    inline bool
> > -    operator>=(const __normal_iterator<_Iterator, _Container>& __lhs,
> > -	       const __normal_iterator<_Iterator, _Container>& __rhs)
> > -    _GLIBCXX_NOEXCEPT
> > -    { return __lhs.base() >= __rhs.base(); }
> > +      __attribute__((__always_inline__)) _GLIBCXX_NODISCARD
> > +      friend
> > +      _GLIBCXX_CONSTEXPR
> > +      inline bool
> > +      operator>=(const __normal_iterator& __lhs, const __normal_iterator& __rhs)
> > +      _GLIBCXX_NOEXCEPT
> > +      { return __lhs.base() >= __rhs.base(); }
> >  #endif // three-way comparison
> >  
> > -  // _GLIBCXX_RESOLVE_LIB_DEFECTS
> > -  // According to the resolution of DR179 not only the various comparison
> > -  // operators but also operator- must accept mixed iterator/const_iterator
> > -  // parameters.
> > -  template<typename _IteratorL, typename _IteratorR, typename _Container>
> > +      // _GLIBCXX_RESOLVE_LIB_DEFECTS
> > +      // 179. Comparison of const_iterators to iterators doesn't work
> > +      // According to the resolution of DR179 not only the various comparison
> > +      // operators but also operator- must accept mixed iterator/const_iterator
> > +      // parameters.
> > +      template<typename _Iter>
> >  #if __cplusplus >= 201103L
> > -    // DR 685.
> > -    [[__nodiscard__, __gnu__::__always_inline__]]
> > -    constexpr auto
> > -    operator-(const __normal_iterator<_IteratorL, _Container>& __lhs,
> > -	      const __normal_iterator<_IteratorR, _Container>& __rhs) noexcept
> > -    -> decltype(__lhs.base() - __rhs.base())
> > +	[[__nodiscard__, __gnu__::__always_inline__]]
> > +	friend
> > +	// _GLIBCXX_RESOLVE_LIB_DEFECTS
> > +	// 685. reverse_iterator/move_iterator difference has invalid signatures
> > +	constexpr auto
> > +	operator-(const __normal_iterator& __lhs,
> > +		  const __normal_iterator<_Iter, _Container>& __rhs) noexcept
> > +	-> decltype(__lhs.base() - __rhs.base())
> >  #else
> > -    inline typename __normal_iterator<_IteratorL, _Container>::difference_type
> > -    operator-(const __normal_iterator<_IteratorL, _Container>& __lhs,
> > -	      const __normal_iterator<_IteratorR, _Container>& __rhs)
> > +	friend
> > +	difference_type
> > +	operator-(const __normal_iterator& __lhs,
> > +		  const __normal_iterator<_Iter, _Container>& __rhs)
> >  #endif
> > -    { return __lhs.base() - __rhs.base(); }
> > +	{ return __lhs.base() - __rhs.base(); }
> >  
> > -  template<typename _Iterator, typename _Container>
> > -    _GLIBCXX_NODISCARD __attribute__((__always_inline__)) _GLIBCXX_CONSTEXPR
> > -    inline typename __normal_iterator<_Iterator, _Container>::difference_type
> > -    operator-(const __normal_iterator<_Iterator, _Container>& __lhs,
> > -	      const __normal_iterator<_Iterator, _Container>& __rhs)
> > -    _GLIBCXX_NOEXCEPT
> > -    { return __lhs.base() - __rhs.base(); }
> > +      __attribute__((__always_inline__)) _GLIBCXX_NODISCARD
> > +      friend
> > +      _GLIBCXX_CONSTEXPR
> > +      inline difference_type
> > +      operator-(const __normal_iterator& __lhs, const __normal_iterator& __rhs)
> > +	_GLIBCXX_NOEXCEPT
> > +      { return __lhs.base() - __rhs.base(); }
> >  
> > -  template<typename _Iterator, typename _Container>
> > -    _GLIBCXX_NODISCARD __attribute__((__always_inline__)) _GLIBCXX_CONSTEXPR
> > -    inline __normal_iterator<_Iterator, _Container>
> > -    operator+(typename __normal_iterator<_Iterator, _Container>::difference_type
> > -	      __n, const __normal_iterator<_Iterator, _Container>& __i)
> > -    _GLIBCXX_NOEXCEPT
> > -    { return __normal_iterator<_Iterator, _Container>(__i.base() + __n); }
> > +      __attribute__((__always_inline__)) _GLIBCXX_NODISCARD
> > +      friend
> > +      _GLIBCXX_CONSTEXPR
> > +      inline __normal_iterator
> > +      operator+(difference_type __n, const __normal_iterator& __i)
> > +	_GLIBCXX_NOEXCEPT
> > +      { return __normal_iterator(__i.base() + __n); }
> > +    };
> >  
> >  _GLIBCXX_END_NAMESPACE_VERSION
> >  } // namespace __gnu_cxx
> > diff --git a/libstdc++-v3/src/c++11/string-inst.cc b/libstdc++-v3/src/c++11/string-inst.cc
> > index ec5ba41ebcd..b8806304949 100644
> > --- a/libstdc++-v3/src/c++11/string-inst.cc
> > +++ b/libstdc++-v3/src/c++11/string-inst.cc
> > @@ -110,14 +110,3 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >  
> >  _GLIBCXX_END_NAMESPACE_VERSION
> >  } // namespace
> > -
> > -namespace __gnu_cxx _GLIBCXX_VISIBILITY(default)
> > -{
> > -_GLIBCXX_BEGIN_NAMESPACE_VERSION
> > -
> > -  using std::S;
> > -  template bool operator==(const S::iterator&, const S::iterator&);
> > -  template bool operator==(const S::const_iterator&, const S::const_iterator&);
> > -
> > -_GLIBCXX_END_NAMESPACE_VERSION
> > -} // namespace
> > -- 
> > 2.47.0
> > 
> > 
>
  
Jonathan Wakely Dec. 2, 2024, 8:39 p.m. UTC | #3
On Mon, 2 Dec 2024 at 17:42, Patrick Palka <ppalka@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.
  
Patrick Palka Dec. 3, 2024, 1:58 a.m. UTC | #4
On Mon, 2 Dec 2024, Jonathan Wakely wrote:

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

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.
> 
>
  

Patch

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&>()));
 
-  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());
+      }
+      { 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
+	operator==(const __normal_iterator& __lhs,
+		   const __normal_iterator<_Iter, _Container>& __rhs)
+	_GLIBCXX_NOEXCEPT
+	{ return __lhs.base() == __rhs.base(); }
 
-  template<typename _Iterator, typename _Container>
-    _GLIBCXX_NODISCARD __attribute__((__always_inline__)) _GLIBCXX_CONSTEXPR
-    inline bool
-    operator==(const __normal_iterator<_Iterator, _Container>& __lhs,
-	       const __normal_iterator<_Iterator, _Container>& __rhs)
-    _GLIBCXX_NOEXCEPT
-    { return __lhs.base() == __rhs.base(); }
+      __attribute__((__always_inline__)) _GLIBCXX_NODISCARD
+      friend
+      _GLIBCXX_CONSTEXPR
+      inline bool
+      operator==(const __normal_iterator& __lhs, const __normal_iterator& __rhs)
+      _GLIBCXX_NOEXCEPT
+      { return __lhs.base() == __rhs.base(); }
 
-  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(); }
+      template<typename _Iter>
+	__attribute__((__always_inline__)) _GLIBCXX_NODISCARD
+	friend
+	_GLIBCXX_CONSTEXPR
+	inline bool
+	operator!=(const __normal_iterator& __lhs,
+		   const __normal_iterator<_Iter, _Container>& __rhs)
+	_GLIBCXX_NOEXCEPT
+	{ return __lhs.base() != __rhs.base(); }
 
-  template<typename _Iterator, typename _Container>
-    _GLIBCXX_NODISCARD __attribute__((__always_inline__)) _GLIBCXX_CONSTEXPR
-    inline bool
-    operator!=(const __normal_iterator<_Iterator, _Container>& __lhs,
-	       const __normal_iterator<_Iterator, _Container>& __rhs)
-    _GLIBCXX_NOEXCEPT
-    { return __lhs.base() != __rhs.base(); }
+      __attribute__((__always_inline__)) _GLIBCXX_NODISCARD
+      friend
+      _GLIBCXX_CONSTEXPR
+      inline bool
+      operator!=(const __normal_iterator& __lhs, const __normal_iterator& __rhs)
+      _GLIBCXX_NOEXCEPT
+      { return __lhs.base() != __rhs.base(); }
 
-  // Random access 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(); }
+      // Random access iterator requirements
+      template<typename _Iter>
+	friend
+	__attribute__((__always_inline__)) _GLIBCXX_NODISCARD _GLIBCXX_CONSTEXPR
+	inline bool
+	operator<(const __normal_iterator& __lhs,
+		  const __normal_iterator<_Iter, _Container>& __rhs)
+	_GLIBCXX_NOEXCEPT
+	{ return __lhs.base() < __rhs.base(); }
 
-  template<typename _Iterator, typename _Container>
-    _GLIBCXX_NODISCARD __attribute__((__always_inline__)) _GLIBCXX20_CONSTEXPR
-    inline bool
-    operator<(const __normal_iterator<_Iterator, _Container>& __lhs,
-	      const __normal_iterator<_Iterator, _Container>& __rhs)
-    _GLIBCXX_NOEXCEPT
-    { return __lhs.base() < __rhs.base(); }
+      __attribute__((__always_inline__)) _GLIBCXX_NODISCARD
+      friend
+      _GLIBCXX20_CONSTEXPR
+      inline bool
+      operator<(const __normal_iterator& __lhs, const __normal_iterator& __rhs)
+      _GLIBCXX_NOEXCEPT
+      { return __lhs.base() < __rhs.base(); }
 
-  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(); }
+      template<typename _Iter>
+	friend
+	__attribute__((__always_inline__)) _GLIBCXX_NODISCARD _GLIBCXX_CONSTEXPR
+	inline bool
+	operator>(const __normal_iterator& __lhs,
+		  const __normal_iterator<_Iter, _Container>& __rhs)
+	_GLIBCXX_NOEXCEPT
+	{ return __lhs.base() > __rhs.base(); }
 
-  template<typename _Iterator, typename _Container>
-    _GLIBCXX_NODISCARD __attribute__((__always_inline__)) _GLIBCXX_CONSTEXPR
-    inline bool
-    operator>(const __normal_iterator<_Iterator, _Container>& __lhs,
-	      const __normal_iterator<_Iterator, _Container>& __rhs)
-    _GLIBCXX_NOEXCEPT
-    { return __lhs.base() > __rhs.base(); }
+      __attribute__((__always_inline__)) _GLIBCXX_NODISCARD
+      friend
+      _GLIBCXX_CONSTEXPR
+      inline bool
+      operator>(const __normal_iterator& __lhs, const __normal_iterator& __rhs)
+      _GLIBCXX_NOEXCEPT
+      { return __lhs.base() > __rhs.base(); }
 
-  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(); }
+      template<typename _Iter>
+	__attribute__((__always_inline__)) _GLIBCXX_NODISCARD
+	friend
+	_GLIBCXX_CONSTEXPR
+	inline bool
+	operator<=(const __normal_iterator& __lhs,
+		   const __normal_iterator<_Iter, _Container>& __rhs)
+	_GLIBCXX_NOEXCEPT
+	{ return __lhs.base() <= __rhs.base(); }
 
-  template<typename _Iterator, typename _Container>
-    _GLIBCXX_NODISCARD __attribute__((__always_inline__)) _GLIBCXX_CONSTEXPR
-    inline bool
-    operator<=(const __normal_iterator<_Iterator, _Container>& __lhs,
-	       const __normal_iterator<_Iterator, _Container>& __rhs)
-    _GLIBCXX_NOEXCEPT
-    { return __lhs.base() <= __rhs.base(); }
+      __attribute__((__always_inline__)) _GLIBCXX_NODISCARD
+      friend
+      _GLIBCXX_CONSTEXPR
+      inline bool
+      operator<=(const __normal_iterator& __lhs, const __normal_iterator& __rhs)
+      _GLIBCXX_NOEXCEPT
+      { return __lhs.base() <= __rhs.base(); }
 
-  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(); }
+      template<typename _Iter>
+	__attribute__((__always_inline__)) _GLIBCXX_NODISCARD
+	friend
+	_GLIBCXX_CONSTEXPR
+	inline bool
+	operator>=(const __normal_iterator& __lhs,
+		   const __normal_iterator<_Iter, _Container>& __rhs)
+	_GLIBCXX_NOEXCEPT
+	{ return __lhs.base() >= __rhs.base(); }
 
-  template<typename _Iterator, typename _Container>
-    _GLIBCXX_NODISCARD __attribute__((__always_inline__)) _GLIBCXX_CONSTEXPR
-    inline bool
-    operator>=(const __normal_iterator<_Iterator, _Container>& __lhs,
-	       const __normal_iterator<_Iterator, _Container>& __rhs)
-    _GLIBCXX_NOEXCEPT
-    { return __lhs.base() >= __rhs.base(); }
+      __attribute__((__always_inline__)) _GLIBCXX_NODISCARD
+      friend
+      _GLIBCXX_CONSTEXPR
+      inline bool
+      operator>=(const __normal_iterator& __lhs, const __normal_iterator& __rhs)
+      _GLIBCXX_NOEXCEPT
+      { return __lhs.base() >= __rhs.base(); }
 #endif // three-way comparison
 
-  // _GLIBCXX_RESOLVE_LIB_DEFECTS
-  // According to the resolution of DR179 not only the various comparison
-  // operators but also operator- must accept mixed iterator/const_iterator
-  // parameters.
-  template<typename _IteratorL, typename _IteratorR, typename _Container>
+      // _GLIBCXX_RESOLVE_LIB_DEFECTS
+      // 179. Comparison of const_iterators to iterators doesn't work
+      // According to the resolution of DR179 not only the various comparison
+      // operators but also operator- must accept mixed iterator/const_iterator
+      // parameters.
+      template<typename _Iter>
 #if __cplusplus >= 201103L
-    // DR 685.
-    [[__nodiscard__, __gnu__::__always_inline__]]
-    constexpr auto
-    operator-(const __normal_iterator<_IteratorL, _Container>& __lhs,
-	      const __normal_iterator<_IteratorR, _Container>& __rhs) noexcept
-    -> decltype(__lhs.base() - __rhs.base())
+	[[__nodiscard__, __gnu__::__always_inline__]]
+	friend
+	// _GLIBCXX_RESOLVE_LIB_DEFECTS
+	// 685. reverse_iterator/move_iterator difference has invalid signatures
+	constexpr auto
+	operator-(const __normal_iterator& __lhs,
+		  const __normal_iterator<_Iter, _Container>& __rhs) noexcept
+	-> decltype(__lhs.base() - __rhs.base())
 #else
-    inline typename __normal_iterator<_IteratorL, _Container>::difference_type
-    operator-(const __normal_iterator<_IteratorL, _Container>& __lhs,
-	      const __normal_iterator<_IteratorR, _Container>& __rhs)
+	friend
+	difference_type
+	operator-(const __normal_iterator& __lhs,
+		  const __normal_iterator<_Iter, _Container>& __rhs)
 #endif
-    { return __lhs.base() - __rhs.base(); }
+	{ return __lhs.base() - __rhs.base(); }
 
-  template<typename _Iterator, typename _Container>
-    _GLIBCXX_NODISCARD __attribute__((__always_inline__)) _GLIBCXX_CONSTEXPR
-    inline typename __normal_iterator<_Iterator, _Container>::difference_type
-    operator-(const __normal_iterator<_Iterator, _Container>& __lhs,
-	      const __normal_iterator<_Iterator, _Container>& __rhs)
-    _GLIBCXX_NOEXCEPT
-    { return __lhs.base() - __rhs.base(); }
+      __attribute__((__always_inline__)) _GLIBCXX_NODISCARD
+      friend
+      _GLIBCXX_CONSTEXPR
+      inline difference_type
+      operator-(const __normal_iterator& __lhs, const __normal_iterator& __rhs)
+	_GLIBCXX_NOEXCEPT
+      { return __lhs.base() - __rhs.base(); }
 
-  template<typename _Iterator, typename _Container>
-    _GLIBCXX_NODISCARD __attribute__((__always_inline__)) _GLIBCXX_CONSTEXPR
-    inline __normal_iterator<_Iterator, _Container>
-    operator+(typename __normal_iterator<_Iterator, _Container>::difference_type
-	      __n, const __normal_iterator<_Iterator, _Container>& __i)
-    _GLIBCXX_NOEXCEPT
-    { return __normal_iterator<_Iterator, _Container>(__i.base() + __n); }
+      __attribute__((__always_inline__)) _GLIBCXX_NODISCARD
+      friend
+      _GLIBCXX_CONSTEXPR
+      inline __normal_iterator
+      operator+(difference_type __n, const __normal_iterator& __i)
+	_GLIBCXX_NOEXCEPT
+      { return __normal_iterator(__i.base() + __n); }
+    };
 
 _GLIBCXX_END_NAMESPACE_VERSION
 } // namespace __gnu_cxx
diff --git a/libstdc++-v3/src/c++11/string-inst.cc b/libstdc++-v3/src/c++11/string-inst.cc
index ec5ba41ebcd..b8806304949 100644
--- a/libstdc++-v3/src/c++11/string-inst.cc
+++ b/libstdc++-v3/src/c++11/string-inst.cc
@@ -110,14 +110,3 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
 _GLIBCXX_END_NAMESPACE_VERSION
 } // namespace
-
-namespace __gnu_cxx _GLIBCXX_VISIBILITY(default)
-{
-_GLIBCXX_BEGIN_NAMESPACE_VERSION
-
-  using std::S;
-  template bool operator==(const S::iterator&, const S::iterator&);
-  template bool operator==(const S::const_iterator&, const S::const_iterator&);
-
-_GLIBCXX_END_NAMESPACE_VERSION
-} // namespace