What is the fully typesafe and most flexible (in terms of constexpr
) way to compare a two integers of two generally unexpected (different) types?
Here's an idea: We need to use "the usual arithmetic conversions":
If both types are unsigned, just compare.
If both types are signed, just compare.
If the signedness differs and the signed value is negative, we're done.
The actual work applies where both values are non-negative and have different signedness. When the unsigned value is larger than the maximal signed value of the signed type, we're done. Otherwise, the unsigned value can be converted to the signed type without changing value, and compared.
Here's an attempt:
#include <type_traits>
#include <limits>
template <bool SameSignedness> struct IntComparerImpl;
template <typename T, typename U>
constexpr bool IntCompare(T x, U y)
{
return IntComparerImpl<std::is_signed<T>::value ==
std::is_signed<U>::value>::compare(x, y);
}
// same signedness case:
template <> struct IntComparerImpl<true>
{
template<typename T, typename U>
static constexpr bool compare(T t, U u)
{
return t < u;
}
};
// different signedness case:
template <> struct IntComparerImpl<false>
{
// I1 is signed, I2 is unsigned
template <typename I1, typename I2>
static constexpr typename std::enable_if<std::is_signed<I1>::value, bool>::type
compare(I1 x, I2 y)
{
return x < 0
|| y > std::numeric_limits<I1>::max()
|| x < static_cast<I1>(y);
}
// I1 is unsigned, I2 is signed
template <typename I1, typename I2>
static typename std::enable_if<std::is_signed<I2>::value, bool>::type
compare(I1 x, I2 y)
{
return !(y < 0)
&& !(x > std::numeric_limits<I2>::max())
&& static_cast<I2>(x) < y;
}
};
My own solution is this (based on N3485.pdf §5):
#include <type_traits>
#include <limits>
#include <utility>
#include <cstdint>
#include <cstdlib>
template< typename L, typename R >
inline constexpr
typename std::enable_if< (std::is_signed< L >::value && !std::is_signed< R >::value), bool >::type
less(L const & lhs, R const & rhs)
{
static_assert(std::is_integral< L >::value,
"lhs value must be of integral type");
static_assert(std::is_integral< R >::value,
"rhs value must be of integral type");
using T = typename std::common_type< L, R >::type;
return (lhs < static_cast< L >(0)) || (static_cast< T const & >(lhs) < static_cast< T const & >(rhs));
}
template< typename L, typename R >
inline constexpr
typename std::enable_if< (!std::is_signed< L >::value && std::is_signed< R >::value), bool >::type
less(L const & lhs, R const & rhs)
{
static_assert(std::is_integral< L >::value,
"lhs value must be of integral type");
static_assert(std::is_integral< R >::value,
"rhs value must be of integral type");
using T = typename std::common_type< L, R >::type;
return !(rhs < static_cast< R >(0)) && (static_cast< T const & >(lhs) < static_cast< T const & >(rhs));
}
template< typename L, typename R >
inline constexpr
typename std::enable_if< (std::is_signed< L >::value == std::is_signed< R >::value), bool >::type
less(L const & lhs, R const & rhs)
{
static_assert(std::is_integral< L >::value,
"lhs value must be of integral type");
static_assert(std::is_integral< R >::value,
"rhs value must be of integral type");
return lhs < rhs;
}
namespace
{
static_assert(less(1, 2), "0");
static_assert(less(-1, std::numeric_limits< std::uintmax_t >::max()), "1");
static_assert(less< std::int8_t, std::uintmax_t >(-1, std::numeric_limits< std::uintmax_t >::max()), "2");
static_assert(less< std::intmax_t, std::uint8_t >(-1, std::numeric_limits< std::uint8_t >::max()), "3");
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wsign-compare"
static_assert(!(-1 < std::numeric_limits< unsigned long >::max()), "4");
#pragma GCC diagnostic pop
static_assert(less(-1, std::numeric_limits< unsigned long >::max()), "5");
}
int main()
{
return EXIT_SUCCESS;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With