On Fri, Apr 25, 2025 at 12:19 AM Jonathan Wakely <jwak...@redhat.com> wrote:

> 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.
>
> 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 // < C++17
>
> 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.
>
LGTM with one very small change to comment.

>
> 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.
> ---
>
> Tested x86_64-linux.
>
>  libstdc++-v3/include/bits/stl_tree.h | 108 +++++++++++++--------------
>  1 file changed, 50 insertions(+), 58 deletions(-)
>
> diff --git a/libstdc++-v3/include/bits/stl_tree.h
> b/libstdc++-v3/include/bits/stl_tree.h
> index 6b35f99a25a..c7352093933 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(); }
>
> +      // _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
> +         // Enforce this here with a user-friendly message.
> +         static_assert(
> +           __is_invocable<const _Compare&, const _Key&, const
> _Key&>::value,
> +           "comparison object must be invocable with two arguments of key
> type"
> +         );
> +# endif // C++17
>
This does not match the condition in if, should be C++11

> +         return _M_impl._M_key_compare(__k1, __k2);
> +       }
> +
>        static const _Key&
>        _S_key(const _Node& __node)
> -      {
> -#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&>{})
> -         static_assert(
> -             is_invocable_v<const _Compare&, const _Key&, const _Key&>,
> -             "comparison object must be invocable as const");
> -# endif // C++17
> -#endif // C++11
>
I checked that _S_key seems to be mostly passed to __M_key_compare.
There is one function _M_key or _Auto_node that calls is, but  it get
passed to
M_get_insert_(hint_)?(equal|uniq)_pos, but that function is called
_M_comapare.
In short, yes, this was duplicate of check.

> -
> -       return _KeyOfValue()(*__node._M_valptr());
> -      }
> +      { 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)
> --
> 2.49.0
>
>

Reply via email to