Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Comparing integers of arbitrary types

Tags:

c++

c++11

What is the fully typesafe and most flexible (in terms of constexpr) way to compare a two integers of two generally unexpected (different) types?

like image 233
Tomilov Anatoliy Avatar asked May 25 '13 19:05

Tomilov Anatoliy


2 Answers

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;
    }
};
like image 118
Kerrek SB Avatar answered Nov 20 '22 23:11

Kerrek SB


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;
}
like image 25
Tomilov Anatoliy Avatar answered Nov 20 '22 21:11

Tomilov Anatoliy