Reputation: 9713
Due to implicit conversion and the fact that std::numeric_limits<T>::max()
and friends return type T
, it seems non-trivial to write a function bool cmp(IntA a, IntB b)
that "does the right thing" to conceptually return a < b;
. That is, if they share a common range, compare, if not, determine if a
is less than b
regardless of number of bits or signedness. Is there a simpler implementation than this naive one?:
template <typename IntA, typename IntB>
[[nodiscard]] constexpr bool cmp(IntA a, IntB b) noexcept {
static_assert(std::is_integral_v<IntA>);
static_assert(sizeof(IntA) <= sizeof(long long int), "We assume we can fit everything into long long");
static_assert(std::is_integral_v<IntB>);
static_assert(sizeof(IntB) <= sizeof(long long int), "We assume we can fit everything into long long");
if (a < 0) {
if (b < 0) {
return static_cast<signed long long int>(a) < static_cast<signed long long int>(b);
} else {
return true;
}
} else {
if (b < 0) {
return false;
} else {
return static_cast<unsigned long long int>(a) < static_cast<unsigned long long int>(b);
}
}
}
https://godbolt.org/z/aPbozGW9j
And what can we do to ensure it gets fully optimized?
Upvotes: 4
Views: 1389
Reputation: 483
How about this
template <typename IntA, typename IntB>
bool cmp(IntA a, IntB b) {
if constexpr (std::is_signed<IntA>::value == std::is_signed<IntB>::value)
{
return a < b;
}
return a < 0 || (b >= 0 && a < b);
}
If both types are of the same signed-ness, then comparing them is safe. If they are not, then:
a < 0
then b >= 0
and we return true.a >= 0
. If b < 0
the expression will return false. If b >= 0
then both are positive and it's safe to return a < b
.https://godbolt.org/z/hPKxoscKn
Upvotes: 1
Reputation: 180945
If you can use C++20, then you can use the newly added functions to do just this. We now have
template< class T, class U >
constexpr bool cmp_equal( T t, U u ) noexcept;
template< class T, class U >
constexpr bool cmp_not_equal( T t, U u ) noexcept;
template< class T, class U >
constexpr bool cmp_less( T t, U u ) noexcept;
template< class T, class U >
constexpr bool cmp_greater( T t, U u ) noexcept;
template< class T, class U >
constexpr bool cmp_less_equal( T t, U u ) noexcept;
template< class T, class U >
constexpr bool cmp_greater_equal( T t, U u ) noexcept;
which
Compare the values of two integers
t
andu
. Unlike builtin comparison operators, negative signed integers always compare less than (and not equal to) unsigned integers: the comparison is safe against lossy integer conversion.-1 > 0u; // true std::cmp_greater(-1, 0u); // false
It is a compile-time error if either
T
orU
is not a signed or unsigned integer type (including standard integer type and extended integer type).
Upvotes: 8
Reputation: 9837
Your jumping through hoops at runtime (via comparing with zero) is only necessary when the signed'ness of the inputs mismatch. So I'd recommend using if constexpr
to help the compiler get it right in only the cases where it might be useful. Something like:
template <typename LargestSignedInt = long long int, typename IntA, typename IntB>
[[nodiscard]] constexpr bool cmp(IntA a, IntB b) noexcept {
static_assert(std::is_integral_v<IntA>);
static_assert(sizeof(IntA) <= sizeof(LargestSignedInt));
static_assert(std::is_integral_v<IntB>);
static_assert(sizeof(IntB) <= sizeof(LargestSignedInt));
if constexpr(sizeof(IntA) < sizeof(LargestSignedInt) && sizeof(IntB) < sizeof(LargestSignedInt))
{
// Both types are smaller than largest signed type supported, just use largest signed type.
return static_cast<LargestSignedInt>(a) < static_cast<LargestSignedInt>(b);
}
if constexpr(std::is_signed_v<IntA> != std::is_signed_v<IntB>)
{
// Signedness mismatch
if (a < 0) {
if (b < 0) {
return static_cast<LargestSignedInt>(a) < static_cast<LargestSignedInt>(b);
} else {
return true;
}
} else {
if (b < 0) {
return false;
} else {
return static_cast<typename std::make_unsigned<LargestSignedInt>::type>(a) < static_cast<typename std::make_unsigned<LargestSignedInt>::type>(b);
}
}
}
if constexpr(std::is_signed_v<IntA>)
{
// Both types are signed.
return static_cast<LargestSignedInt>(a) < static_cast<LargestSignedInt>(b);
}
// Both types are unsigned.
return static_cast<typename std::make_unsigned<LargestSignedInt>::type>(a) < static_cast<typename std::make_unsigned<LargestSignedInt>::type>(b);
}
Upvotes: 0