2013-05-25 37 views
12

什么是完全类型安全和最灵活的(根据constexpr)方式来比较两个通常意外(不同)类型的整数?比较任意类型的整数

+3

似乎对代码审查更适合我 –

+1

^是 - 如果你为此创建了单元测试,并产生了意想不到的行为,那么这将是正确的地方。 –

+1

查看['std :: common_type'](http://en.cppreference.com/w/cpp/types/common_type)。这会给你一个你可以在比较之前投给双方的类型。 – cHao

回答

12

这里有一个想法:我们需要使用“通常的算术转换”:

  • 如果两种类型是无符号,只是比较。

  • 如果两种类型都有符号,就比较一下。

  • 如果签名的不同并且签名值为负数,我们就完成了。

  • 实际工作适用于两个值都是非负数且签名不同的情况。当无符号值大于签名类型的最大有符号值时,我们就完成了。否则,可以将无符号值转换为带符号类型而不更改值并进行比较。

下面是一个尝试:

#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>(y) < x; 
    } 
}; 
+0

“当无符号值大于有符号类型的最大符号值”看来,这里是某种逻辑递归。 – Orient

+0

关于“带符号类型的最大有符号值”与无符号值比较的注意事项 – Orient

+0

@Dukales:怎么样?假设你比较了无符号整数200和有符号字符50.由于200大于最大符号字符127,所以我们完成了。 –

1

My own solution这是(根据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; 
}