Reputation: 711
For example, the compare of std::less
is defined as a template struct
template< class T = void >
struct less;
While std::compare_three_way
is defined just as a normal struct, and its operator()
is a template function. (code from MSVC)
struct compare_three_way {
template <class _Ty1, class _Ty2>
requires three_way_comparable_with<_Ty1, _Ty2> // TRANSITION, GH-489
constexpr auto operator()(_Ty1&& _Left, _Ty2&& _Right) const
noexcept(noexcept(_STD forward<_Ty1>(_Left) <=> _STD forward<_Ty2>(_Right))) /* strengthened */ {
return _STD forward<_Ty1>(_Left) <=> _STD forward<_Ty2>(_Right);
}
using is_transparent = int;
};
So why isn't std::compare_three_way
a template struct?
template <class _Ty1, class _Ty2>
requires three_way_comparable_with<_Ty1, _Ty2>
struct compare_three_way {
constexpr auto operator()(_Ty1&& _Left, _Ty2&& _Right) const;
};
By the way, could I replace std::less<T>
by std::three_way_compare
in my own implementation of containers, like Comparer<T>
in C#.
Upvotes: 3
Views: 428
Reputation: 381
As it looks like the std::compare_three_way
performs forwarding, it could not work when the template parameters are for a struct
. C++ Standard section 13.10.3.2 §3:
A forwarding reference is an rvalue reference to a cv-unqualified template parameter that does not represent a template parameter of a class template (during class template argument deduction ([over.match.class.deduct])).
Thus, here _Ty1&&
and _Ty2&&
are just rvalue references, and there is no way to forward them correctly:
template <class _Ty1, class _Ty2>
requires three_way_comparable_with<_Ty1, _Ty2>
struct compare_three_way {
constexpr auto operator()(_Ty1&& _Left, _Ty2&& _Right) const;
};
The template struct declared above only accepts rvalue expressions as its operands, which would not be too useful. The only way to have this struct
be usable is to make the arguments const
references. The MSVC implementers have opted for the forwarding way instead.
Upvotes: 1
Reputation: 238401
The comparison of original std::less
(and its friends) are defined as such:
bool operator()( const T& lhs, const T& rhs ) const;
As this function call operator is not a template, it can only compare objects of the type that is used to instantiate the std::less
template.
In a later language revision, these comparators were extended with specialisation std::less<>
to support comparing objects of different types by templating the the function call operator itself:
template< class T, class U>
constexpr auto operator()( T&& lhs, U&& rhs ) const
-> decltype(std::forward<T>(lhs) < std::forward<U>(rhs));
This largely obsoletes the homogeneous versions (std::less<T>
) in most use cases because it is either equivalent, or more efficient due to not forcing a conversion to a common type. The old homogeneous comparators are retained for backward compatibility.
Heterogeneous lookup was an existing thing when std::compare_three_way
was proposed, so the homogeneous version was never introduced.
To use it in template, like
std::less<T>
instd::set<T>
You can use std::compare_three_way
in std::set<T>
and std::set<U>
etc. (figuratively) just like you can use std::less<>
. You (probably) don't need std::less<T>
nor std::less<U>
- nor std::compare_three_way<T>
which doesn't exist.
Upvotes: 7