Files
gcc/libstdc++-v3/include/bits/ranges_cmp.h
Tomasz Kamiński 8fad43b785 libstdc++: Use overload operator<=> when provided in relational functors [PR114153]
The implementation of less<> did not consider the possibility of t < u being
rewritten from overloaded operator<=>. This lead to situation when for t,u that:
* provide overload operator<=>, such that (t < u) is rewritten to (t <=> u) < 0,
* are convertible to pointers,
the expression std::less<>(t, u) would incorrectly result in call of
std::less<void*> on values converted to the pointers, instead of t < u.
The similar issues also occurred for greater<>, less_equal<>, greater_equal<>,
their range equivalents, and in three_way_compare for heterogeneous calls.

This patch addresses above, by also checking for free-functions and member
overloads of operator<=>, before falling back to pointer comparison. We do
not put any constraints on the return type of selected operator, in particular
in being one of the standard defined comparison categories, as the language
does not put any restriction of returned type, and if (t <=> u) is well
formed, (t op u) is interpreted as (t <=> u) op 0. If that later expression
is ill-formed, the expression using op also is (see included tests).

The relational operator rewrites try both order of arguments, t < u,
can be rewritten into operator<=>(t, u) < 0 or 0 < operator<=>(u, t), it
means that we need to test both operator<=>(T, U) and operator<=>(U, T)
if T and U are not the same types. This is now extracted into
__not_overloaded_spaceship helper concept, placed in <concepts>, to
avoid extending set of includes.

The compare_three_way functor defined in compare, already considers overloaded
operator<=>, however it does not consider reversed candidates, leading
to situation in which t <=> u results in 0 <=> operator<=>(u, t), while
compare_three_way{}(t, u) uses pointer comparison. This is also addressed by
using __not_overloaded_spaceship, that check both order of arguments.

Finally, as operator<=> is introduced in C++20, for std::less(_equal)?<>,
std::greater(_equal)?<>, we use provide separate __ptr_cmp implementation
in that mode, that relies on use of requires expression. We use a nested
requires clause to guarantee short-circuiting of their evaluation.
The operator() of aforementioned functors is reworked to use if constexpr,
in all standard modes (as we allow is as extension), eliminating the need
for _S_cmp function.

	PR libstdc++/114153

libstdc++-v3/ChangeLog:

	* include/bits/ranges_cmp.h (__detail::__less_builtin_ptr_cmp):
	Add __not_overloaded_spaceship spaceship check.
	* include/bits/stl_function.h (greater<void>::operator())
	(less<void>::operator(), greater_equal<void>::operator())
	(less_equal<void>::operator()): Implement using if constexpr.
	(greater<void>::__S_cmp, less<void>::__S_cmp)
	(greater_equal<void>::__ptr_comp, less_equal<void>::S_cmp):
	Remove.
	(greater<void>::__ptr_cmp, less<void>::__ptr_cmp)
	(greater_equal<void>::__ptr_comp, less_equal<void>::ptr_cmp): Change
	tostatic constexpr variable. Define in terms of requires expressions
	and __not_overloaded_spaceship check.
	* include/std/concepts: (__detail::__not_overloaded_spaceship):
	Define.
	* libsupc++/compare: (__detail::__3way_builtin_ptr_cmp): Use
	__not_overloaded_spaceship concept.
	* testsuite/20_util/function_objects/comparisons_pointer_spaceship.cc: New test.

Reviewed-by: Jonathan Wakely <jwakely@redhat.com>
Signed-off-by: Tomasz Kamiński <tkaminsk@redhat.com>
2026-01-19 12:09:11 +01:00

187 lines
6.0 KiB
C++

// Concept-constrained comparison implementations -*- C++ -*-
// Copyright (C) 2019-2026 Free Software Foundation, Inc.
//
// This file is part of the GNU ISO C++ Library. This library is free
// software; you can redistribute it and/or modify it under the
// terms of the GNU General Public License as published by the
// Free Software Foundation; either version 3, or (at your option)
// any later version.
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// Under Section 7 of GPL version 3, you are granted additional
// permissions described in the GCC Runtime Library Exception, version
// 3.1, as published by the Free Software Foundation.
// You should have received a copy of the GNU General Public License and
// a copy of the GCC Runtime Library Exception along with this program;
// see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
// <http://www.gnu.org/licenses/>.
/** @file bits/ranges_cmp.h
* This is an internal header file, included by other library headers.
* Do not attempt to use it directly. @headername{functional}
*/
#ifndef _RANGES_CMP_H
#define _RANGES_CMP_H 1
#if __cplusplus > 201703L
# include <bits/move.h>
# include <concepts>
namespace std _GLIBCXX_VISIBILITY(default)
{
_GLIBCXX_BEGIN_NAMESPACE_VERSION
struct __is_transparent; // not defined
// Define std::identity here so that <iterator> and <ranges>
// don't need to include <bits/stl_function.h> to get it.
/// [func.identity] The identity function.
struct identity
{
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wc++23-extensions" // static operator()
template<typename _Tp>
[[nodiscard]]
static constexpr _Tp&&
operator()(_Tp&& __t) noexcept
{ return std::forward<_Tp>(__t); }
#pragma GCC diagnostic pop
using is_transparent = __is_transparent;
};
#ifdef __glibcxx_ranges // C++ >= 20
namespace ranges
{
namespace __detail
{
// BUILTIN-PTR-CMP(T, <, U)
// This determines whether t < u results in a call to a built-in operator<
// comparing pointers. It doesn't work for function pointers (PR 93628).
template<typename _Tp, typename _Up>
concept __less_builtin_ptr_cmp
= requires (_Tp&& __t, _Up&& __u) { { __t < __u } -> same_as<bool>; }
&& convertible_to<_Tp, const volatile void*>
&& convertible_to<_Up, const volatile void*>
&& ! requires(_Tp&& __t, _Up&& __u)
{ operator<(std::forward<_Tp>(__t), std::forward<_Up>(__u)); }
&& ! requires(_Tp&& __t, _Up&& __u)
{ std::forward<_Tp>(__t).operator<(std::forward<_Up>(__u)); }
&& std::__detail::__not_overloaded_spaceship<_Tp, _Up>;
} // namespace __detail
// [range.cmp] Concept-constrained comparisons
// _GLIBCXX_RESOLVE_LIB_DEFECTS
// 3530 BUILTIN-PTR-MEOW should not opt the type out of syntactic checks
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wc++23-extensions" // static operator()
/// ranges::equal_to function object type.
struct equal_to
{
template<typename _Tp, typename _Up>
requires equality_comparable_with<_Tp, _Up>
static constexpr bool
operator()(_Tp&& __t, _Up&& __u)
noexcept(noexcept(std::declval<_Tp>() == std::declval<_Up>()))
{ return std::forward<_Tp>(__t) == std::forward<_Up>(__u); }
using is_transparent = __is_transparent;
};
/// ranges::not_equal_to function object type.
struct not_equal_to
{
template<typename _Tp, typename _Up>
requires equality_comparable_with<_Tp, _Up>
static constexpr bool
operator()(_Tp&& __t, _Up&& __u)
noexcept(noexcept(std::declval<_Tp>() == std::declval<_Up>()))
{ return !equal_to{}(std::forward<_Tp>(__t), std::forward<_Up>(__u)); }
using is_transparent = __is_transparent;
};
/// ranges::less function object type.
struct less
{
template<typename _Tp, typename _Up>
requires totally_ordered_with<_Tp, _Up>
static constexpr bool
operator()(_Tp&& __t, _Up&& __u)
noexcept(noexcept(std::declval<_Tp>() < std::declval<_Up>()))
{
if constexpr (__detail::__less_builtin_ptr_cmp<_Tp, _Up>)
{
if (std::__is_constant_evaluated())
return __t < __u;
auto __x = reinterpret_cast<__UINTPTR_TYPE__>(
static_cast<const volatile void*>(std::forward<_Tp>(__t)));
auto __y = reinterpret_cast<__UINTPTR_TYPE__>(
static_cast<const volatile void*>(std::forward<_Up>(__u)));
return __x < __y;
}
else
return std::forward<_Tp>(__t) < std::forward<_Up>(__u);
}
using is_transparent = __is_transparent;
};
/// ranges::greater function object type.
struct greater
{
template<typename _Tp, typename _Up>
requires totally_ordered_with<_Tp, _Up>
static constexpr bool
operator()(_Tp&& __t, _Up&& __u)
noexcept(noexcept(std::declval<_Up>() < std::declval<_Tp>()))
{ return less{}(std::forward<_Up>(__u), std::forward<_Tp>(__t)); }
using is_transparent = __is_transparent;
};
/// ranges::greater_equal function object type.
struct greater_equal
{
template<typename _Tp, typename _Up>
requires totally_ordered_with<_Tp, _Up>
static constexpr bool
operator()(_Tp&& __t, _Up&& __u)
noexcept(noexcept(std::declval<_Tp>() < std::declval<_Up>()))
{ return !less{}(std::forward<_Tp>(__t), std::forward<_Up>(__u)); }
using is_transparent = __is_transparent;
};
/// ranges::less_equal function object type.
struct less_equal
{
template<typename _Tp, typename _Up>
requires totally_ordered_with<_Tp, _Up>
static constexpr bool
operator()(_Tp&& __t, _Up&& __u)
noexcept(noexcept(std::declval<_Up>() < std::declval<_Tp>()))
{ return !less{}(std::forward<_Up>(__u), std::forward<_Tp>(__t)); }
using is_transparent = __is_transparent;
};
#pragma GCC diagnostic pop
} // namespace ranges
#endif // __glibcxx_ranges
_GLIBCXX_END_NAMESPACE_VERSION
} // namespace std
#endif // C++20
#endif // _RANGES_CMP_H