[gcc r16-131] libstdc++: Add _M_key_compare helper to associative containers

Jonathan Wakely redi@gcc.gnu.org
Fri Apr 25 10:47:39 GMT 2025


https://gcc.gnu.org/g:876d1a22dfaf873d167bd2ffad190a1d07a81b22

commit r16-131-g876d1a22dfaf873d167bd2ffad190a1d07a81b22
Author: Jonathan Wakely <jwakely@redhat.com>
Date:   Thu Apr 24 14:58:58 2025 +0100

    libstdc++: Add _M_key_compare helper to associative containers
    
    In r10-452-ge625ccc21a91f3 I noted that we don't have an accessor for
    invoking _M_impl._M_key_compare in the associative containers. That
    meant that the static assertions to check for valid comparison functions
    were squirrelled away in _Rb_tree::_S_key instead. As Jason noted in
    https://gcc.gnu.org/pipermail/gcc-patches/2025-April/681436.html this
    means that the static assertions fail later than we'd like.
    
    This change adds a new _Rb_tree::_M_key_compare member function which
    invokes the _M_impl._M_key_compare function object, and then moves the
    static_assert from _S_key into _M_key_compare. Now if the static_assert
    fails, that's the first error we get, before the "no match for call" and
    and "invalid conversion" errors.
    
    Because the new function is const-qualified, we now treat LWG 2542 as a
    DR for older standards, requiring the comparison function to be const
    invocable. Previously we only enforced the LWG 2542 rule for C++17 and
    later.
    
    I did consider deprecating support for comparisons which aren't const
    invocable, something like this:
    
      // Before LWG 2542 it wasn't strictly necessary for _Compare to be
      // const invocable, if you only used non-const container members.
      // Define a non-const overload for pre-C++17, deprecated for C++11/14.
      #if __cplusplus < 201103L
      bool
      _M_key_compare(const _Key& __k1, const _Key& __k2)
      { return _M_impl._M_key_compare(__k1, __k2); }
      #elif __cplusplus < 201703L
      template<typename _Key1, typename _Key2>
        [[__deprecated__("support for comparison functions that are not "
                         "const invocable is deprecated")]]
        __enable_if_t<
        __and_<__is_invocable<_Compare&, const _Key1&, const _Key2&>,
               __not_<__is_invocable<const _Compare&, const _Key1&, const _Key2&>>>::value,
               bool>
        _M_key_compare(const _Key1& __k1, const _Key2& __k2)
        {
          static_assert(
            __is_invocable<_Compare&, const _Key&, const _Key&>::value,
            "comparison object must be invocable with two arguments of key type"
          );
          return _M_impl._M_key_compare(__k1, __k2);
        }
      #endif
    
    But I decided that this isn't necessary, because we've been enforcing
    the C++17 rule since GCC 8.4 and 9.2, and C++17 has been the default
    since GCC 11.1. Users have had plenty of time to fix their invalid
    comparison functions.
    
    libstdc++-v3/ChangeLog:
    
            * include/bits/stl_tree.h (_Rb_tree::_M_key_compare): New member
            function to invoke comparison function.
            (_Rb_tree): Use new member function instead of accessing the
            comparison function directly.
    
    Reviewed-by: Tomasz Kamiński <tkaminsk@redhat.com>

Diff:
---
 libstdc++-v3/include/bits/stl_tree.h | 106 ++++++++++++++++-------------------
 1 file changed, 49 insertions(+), 57 deletions(-)

diff --git a/libstdc++-v3/include/bits/stl_tree.h b/libstdc++-v3/include/bits/stl_tree.h
index 6b35f99a25a3..6caf937cca95 100644
--- a/libstdc++-v3/include/bits/stl_tree.h
+++ b/libstdc++-v3/include/bits/stl_tree.h
@@ -1390,27 +1390,25 @@ namespace __rb_tree
       _M_end() const _GLIBCXX_NOEXCEPT
       { return this->_M_impl._M_header._M_base_ptr(); }
 
-      static const _Key&
-      _S_key(const _Node& __node)
-      {
+      // _GLIBCXX_RESOLVE_LIB_DEFECTS
+      // 2542. Missing const requirements for associative containers
+      template<typename _Key1, typename _Key2>
+	bool
+	_M_key_compare(const _Key1& __k1, const _Key2& __k2) const
+	{
 #if __cplusplus >= 201103L
-	// If we're asking for the key we're presumably using the comparison
-	// object, and so this is a good place to sanity check it.
-	static_assert(__is_invocable<_Compare&, const _Key&, const _Key&>{},
-		      "comparison object must be invocable "
-		      "with two arguments of key type");
-# if __cplusplus >= 201703L
-	// _GLIBCXX_RESOLVE_LIB_DEFECTS
-	// 2542. Missing const requirements for associative containers
-	if constexpr (__is_invocable<_Compare&, const _Key&, const _Key&>{})
+	  // Enforce this here with a user-friendly message.
 	  static_assert(
-	      is_invocable_v<const _Compare&, const _Key&, const _Key&>,
-	      "comparison object must be invocable as const");
-# endif // C++17
-#endif // C++11
+	    __is_invocable<const _Compare&, const _Key&, const _Key&>::value,
+	    "comparison object must be invocable with two arguments of key type"
+	  );
+#endif
+	  return _M_impl._M_key_compare(__k1, __k2);
+	}
 
-	return _KeyOfValue()(*__node._M_valptr());
-      }
+      static const _Key&
+      _S_key(const _Node& __node)
+      { return _KeyOfValue()(*__node._M_valptr()); }
 
       static const _Key&
       _S_key(_Base_ptr __x)
@@ -1933,7 +1931,7 @@ namespace __rb_tree
 	_M_find_tr(const _Kt& __k) const
 	{
 	  const_iterator __j(_M_lower_bound_tr(__k));
-	  if (__j != end() && _M_impl._M_key_compare(__k, _S_key(__j._M_node)))
+	  if (__j != end() && _M_key_compare(__k, _S_key(__j._M_node)))
 	    __j = end();
 	  return __j;
 	}
@@ -1955,7 +1953,7 @@ namespace __rb_tree
 	  auto __x = _M_begin();
 	  auto __y = _M_end();
 	  while (__x)
-	    if (!_M_impl._M_key_compare(_S_key(__x), __k))
+	    if (!_M_key_compare(_S_key(__x), __k))
 	      {
 		__y = __x;
 		__x = _S_left(__x);
@@ -1973,7 +1971,7 @@ namespace __rb_tree
 	  auto __x = _M_begin();
 	  auto __y = _M_end();
 	  while (__x)
-	    if (_M_impl._M_key_compare(__k, _S_key(__x)))
+	    if (_M_key_compare(__k, _S_key(__x)))
 	      {
 		__y = __x;
 		__x = _S_left(__x);
@@ -2474,8 +2472,8 @@ namespace __rb_tree
 		 _NodeGen& __node_gen)
       {
 	bool __insert_left = (__x || __p == _M_end()
-			      || _M_impl._M_key_compare(_KeyOfValue()(__v),
-							_S_key(__p)));
+			      || _M_key_compare(_KeyOfValue()(__v),
+						_S_key(__p)));
 
 	_Base_ptr __z =
 	  __node_gen(_GLIBCXX_FORWARD(_Arg, __v))->_M_base_ptr();
@@ -2500,8 +2498,8 @@ namespace __rb_tree
 #endif
     {
       bool __insert_left = (__p == _M_end()
-			    || !_M_impl._M_key_compare(_S_key(__p),
-						       _KeyOfValue()(__v)));
+			    || !_M_key_compare(_S_key(__p),
+					       _KeyOfValue()(__v)));
 
       _Base_ptr __z =
 	_M_create_node(_GLIBCXX_FORWARD(_Arg, __v))->_M_base_ptr();
@@ -2529,7 +2527,7 @@ namespace __rb_tree
       while (__x)
 	{
 	  __y = __x;
-	  __x = !_M_impl._M_key_compare(_S_key(__x), _KeyOfValue()(__v)) ?
+	  __x = !_M_key_compare(_S_key(__x), _KeyOfValue()(__v)) ?
 		_S_left(__x) : _S_right(__x);
 	}
       return _M_insert_lower(__y, _GLIBCXX_FORWARD(_Arg, __v));
@@ -2601,7 +2599,7 @@ namespace __rb_tree
 		   const _Key& __k) const
     {
       while (__x)
-	if (!_M_impl._M_key_compare(_S_key(__x), __k))
+	if (!_M_key_compare(_S_key(__x), __k))
 	  __y = __x, __x = _S_left(__x);
 	else
 	  __x = _S_right(__x);
@@ -2617,7 +2615,7 @@ namespace __rb_tree
 		   const _Key& __k) const
     {
       while (__x)
-	if (_M_impl._M_key_compare(__k, _S_key(__x)))
+	if (_M_key_compare(__k, _S_key(__x)))
 	  __y = __x, __x = _S_left(__x);
 	else
 	  __x = _S_right(__x);
@@ -2639,9 +2637,9 @@ namespace __rb_tree
       _Base_ptr __y = _M_end();
       while (__x)
 	{
-	  if (_M_impl._M_key_compare(_S_key(__x), __k))
+	  if (_M_key_compare(_S_key(__x), __k))
 	    __x = _S_right(__x);
-	  else if (_M_impl._M_key_compare(__k, _S_key(__x)))
+	  else if (_M_key_compare(__k, _S_key(__x)))
 	    __y = __x, __x = _S_left(__x);
 	  else
 	    {
@@ -2671,9 +2669,9 @@ namespace __rb_tree
       _Base_ptr __y = _M_end();
       while (__x)
 	{
-	  if (_M_impl._M_key_compare(_S_key(__x), __k))
+	  if (_M_key_compare(_S_key(__x), __k))
 	    __x = _S_right(__x);
-	  else if (_M_impl._M_key_compare(__k, _S_key(__x)))
+	  else if (_M_key_compare(__k, _S_key(__x)))
 	    __y = __x, __x = _S_left(__x);
 	  else
 	    {
@@ -2737,7 +2735,7 @@ namespace __rb_tree
       while (__x)
 	{
 	  __y = __x;
-	  __comp = _M_impl._M_key_compare(__k, _S_key(__x));
+	  __comp = _M_key_compare(__k, _S_key(__x));
 	  __x = __comp ? _S_left(__x) : _S_right(__x);
 	}
       iterator __j = iterator(__y);
@@ -2748,7 +2746,7 @@ namespace __rb_tree
 	  else
 	    --__j;
 	}
-      if (_M_impl._M_key_compare(_S_key(__j._M_node), __k))
+      if (_M_key_compare(_S_key(__j._M_node), __k))
 	return _Res(__x, __y);
       return _Res(__j._M_node, _Base_ptr());
     }
@@ -2768,8 +2766,7 @@ namespace __rb_tree
       while (__x)
 	{
 	  __y = __x;
-	  __x = _M_impl._M_key_compare(__k, _S_key(__x)) ?
-		_S_left(__x) : _S_right(__x);
+	  __x = _M_key_compare(__k, _S_key(__x)) ? _S_left(__x) : _S_right(__x);
 	}
       return _Res(__x, __y);
     }
@@ -2838,19 +2835,18 @@ namespace __rb_tree
       // end()
       if (__position._M_node == _M_end())
 	{
-	  if (size() > 0
-	      && _M_impl._M_key_compare(_S_key(_M_rightmost()), __k))
+	  if (size() > 0 && _M_key_compare(_S_key(_M_rightmost()), __k))
 	    return _Res(_Base_ptr(), _M_rightmost());
 	  else
 	    return _M_get_insert_unique_pos(__k);
 	}
-      else if (_M_impl._M_key_compare(__k, _S_key(__position._M_node)))
+      else if (_M_key_compare(__k, _S_key(__position._M_node)))
 	{
 	  // First, try before...
 	  iterator __before(__position._M_node);
 	  if (__position._M_node == _M_leftmost()) // begin()
 	    return _Res(_M_leftmost(), _M_leftmost());
-	  else if (_M_impl._M_key_compare(_S_key((--__before)._M_node), __k))
+	  else if (_M_key_compare(_S_key((--__before)._M_node), __k))
 	    {
 	      if (!_S_right(__before._M_node))
 		return _Res(_Base_ptr(), __before._M_node);
@@ -2860,13 +2856,13 @@ namespace __rb_tree
 	  else
 	    return _M_get_insert_unique_pos(__k);
 	}
-      else if (_M_impl._M_key_compare(_S_key(__position._M_node), __k))
+      else if (_M_key_compare(_S_key(__position._M_node), __k))
 	{
 	  // ... then try after.
 	  iterator __after(__position._M_node);
 	  if (__position._M_node == _M_rightmost())
 	    return _Res(_Base_ptr(), _M_rightmost());
-	  else if (_M_impl._M_key_compare(__k, _S_key((++__after)._M_node)))
+	  else if (_M_key_compare(__k, _S_key((++__after)._M_node)))
 	    {
 	      if (!_S_right(__position._M_node))
 		return _Res(_Base_ptr(), __position._M_node);
@@ -2923,18 +2919,18 @@ namespace __rb_tree
       if (__position._M_node == _M_end())
 	{
 	  if (size() > 0
-	      && !_M_impl._M_key_compare(__k, _S_key(_M_rightmost())))
+	      && !_M_key_compare(__k, _S_key(_M_rightmost())))
 	    return _Res(_Base_ptr(), _M_rightmost());
 	  else
 	    return _M_get_insert_equal_pos(__k);
 	}
-      else if (!_M_impl._M_key_compare(_S_key(__position._M_node), __k))
+      else if (!_M_key_compare(_S_key(__position._M_node), __k))
 	{
 	  // First, try before...
 	  iterator __before(__position._M_node);
 	  if (__position._M_node == _M_leftmost()) // begin()
 	    return _Res(_M_leftmost(), _M_leftmost());
-	  else if (!_M_impl._M_key_compare(__k, _S_key((--__before)._M_node)))
+	  else if (!_M_key_compare(__k, _S_key((--__before)._M_node)))
 	    {
 	      if (!_S_right(__before._M_node))
 		return _Res(_Base_ptr(), __before._M_node);
@@ -2950,7 +2946,7 @@ namespace __rb_tree
 	  iterator __after(__position._M_node);
 	  if (__position._M_node == _M_rightmost())
 	    return _Res(_Base_ptr(), _M_rightmost());
-	  else if (!_M_impl._M_key_compare(_S_key((++__after)._M_node), __k))
+	  else if (!_M_key_compare(_S_key((++__after)._M_node), __k))
 	    {
 	      if (!_S_right(__position._M_node))
 		return _Res(_Base_ptr(), __position._M_node);
@@ -2999,8 +2995,7 @@ namespace __rb_tree
     -> iterator
     {
       bool __insert_left = (__x || __p == _M_end()
-			    || _M_impl._M_key_compare(_S_key(__z),
-						      _S_key(__p)));
+			    || _M_key_compare(_S_key(__z), _S_key(__p)));
 
       _Base_ptr __base_z = __z->_M_base_ptr();
       _Node_traits::_S_insert_and_rebalance
@@ -3017,8 +3012,7 @@ namespace __rb_tree
     -> iterator
     {
       bool __insert_left = (__p == _M_end()
-			    || !_M_impl._M_key_compare(_S_key(__p),
-						       _S_key(__z)));
+			    || !_M_key_compare(_S_key(__p), _S_key(__z)));
 
       _Base_ptr __base_z = __z->_M_base_ptr();
       _Node_traits::_S_insert_and_rebalance
@@ -3039,7 +3033,7 @@ namespace __rb_tree
       while (__x)
 	{
 	  __y = __x;
-	  __x = !_M_impl._M_key_compare(_S_key(__x), _S_key(__z)) ?
+	  __x = !_M_key_compare(_S_key(__x), _S_key(__z)) ?
 		_S_left(__x) : _S_right(__x);
 	}
       return _M_insert_lower_node(__y, __z);
@@ -3151,8 +3145,7 @@ namespace __rb_tree
     {
       iterator __j(_M_lower_bound(_M_begin(), _M_end(), __k));
       return (__j == end()
-	      || _M_impl._M_key_compare(__k,
-					_S_key(__j._M_node))) ? end() : __j;
+	      || _M_key_compare(__k, _S_key(__j._M_node))) ? end() : __j;
     }
 
   template<typename _Key, typename _Val, typename _KeyOfValue,
@@ -3164,8 +3157,7 @@ namespace __rb_tree
     {
       const_iterator __j(_M_lower_bound(_M_begin(), _M_end(), __k));
       return (__j == end()
-	      || _M_impl._M_key_compare(__k,
-					_S_key(__j._M_node))) ? end() : __j;
+	      || _M_key_compare(__k, _S_key(__j._M_node))) ? end() : __j;
     }
 
   template<typename _Key, typename _Val, typename _KeyOfValue,
@@ -3205,9 +3197,9 @@ namespace __rb_tree
 		|| (__R && __R->_M_color == _S_red))
 	      return false;
 
-	  if (__L && _M_impl._M_key_compare(_S_key(__x), _S_key(__L)))
+	  if (__L && _M_key_compare(_S_key(__x), _S_key(__L)))
 	    return false;
-	  if (__R && _M_impl._M_key_compare(_S_key(__R), _S_key(__x)))
+	  if (__R && _M_key_compare(_S_key(__R), _S_key(__x)))
 	    return false;
 
 	  if (!__L && !__R && _Rb_tree_black_count(__x, _M_root()) != __len)


More information about the Libstdc++-cvs mailing list