Safer Comparisons of Signed and Unsigned Integers in C++

by James W. Walker
6 May 2013

The Problem

If you write C++ code like this

static void Foo( int x, unsigned int y )
{
    if (x < y)
    ...

then your compiler may issue a warning like "comparison between signed and unsigned integer expressions". Briefly, the problem is that a standard C++ compiler will handle this code by casting x as an unsigned int before doing the comparison, which may turn a negative number into a large positive number. See a CERT recommendation for more detail.

Simple Fixes

Now, how are we going to fix the warning? We could typecast x as unsigned; this is the same as what the compiler would do, but making the typecast explicit will make the warning go away. However this has the same problem as the original code. Alternately, we could typecast y as signed. This has a different problem: If y is too large, then converting it to a signed integer may produce a negative number, again possibily changing the result of the comparison. In a specific instance, you may be able to deduce that x could never really be negative, or that y could never be too large, but a more general solution would be less error-prone. You could perhaps cast y to a signed type with more bits. But the most general solution is to add a bit more logic:

static void Foo( int x, unsigned int y )
{
    if ( (x < 0) || (static_cast<unsigned int>(x) < y) )
    ...

If the signed and unsigned numbers are swapped, the logic changes:

static void Foo( unsigned int x, int y )
{
    if ( (y >= 0) && (x < static_cast<unsigned int>(y)) )
    ...

Function Overloading

One may wonder why compilers don't automatically generate the necessary code, and I don't know the answer. The next question is how to make it easier to fix these situations. You could write functions like these:

static bool SafeLess( int x, unsigned int y )
{
    return (x < 0) || (static_cast<unsigned int>(x) < y);
}

static bool SafeLess( unsigned int x, int y )
{
    return (y >= 0) && (x < static_cast<unsigned int>(y));
}

Then you could replace direct comparisons with calls to SafeLess. The only trouble is that for a complete solution, you'd need to write more overloads to handle char, short, long, and long long in all possible combinations of signed and unsigned.

A Template Solution

It is possible to handle all the cases using template specialization rather than function overloading. It is not possible to do partial specialization of a template function, but it is possible to do partial specialization of a template class that has a static method.

For a template solution, first let's classify numeric types as signed integers, unsigned integers, or other. With the following template, NumType<T>::Code will be 0 when T is an unsigned integer type, 1 for a signed integer type, and 3 for signed non-integer types such as float.

#include <limits>

template <typename T>
struct NumType
{
    enum {
        Code = (std::numeric_limits<T>::is_integer? 0 : 2) +
            (std::numeric_limits<T>::is_signed? 1 : 0)
    };
};

Next, here is a template that will convert a signed type to an unsigned type with the same number of bits. We use a macro to reduce the amount of text devoted to template specializations.

template <typename T>
struct ToUnsigned;

#define TOUNSIGNED( sType, uType )	\
template <>	\
struct ToUnsigned<sType>  \
{   \
    typedef uType Type;  \
};

TOUNSIGNED( char, unsigned char )
TOUNSIGNED( signed char, unsigned char )
TOUNSIGNED( short, unsigned short )
TOUNSIGNED( int, unsigned int )
TOUNSIGNED( long, unsigned long )
TOUNSIGNED( long long, unsigned long long )
#undef TOUNSIGNED

Note that char is a distinct type that may be signed or unsigned. We include a specialization for it in case it is signed.

We are now ready to write a template class with a static method, and partial specializations for cases where the parameters differ in signedness. Note that the third and fourth template parameters are non-type parameters.

template <typename I, typename J, int typeCodeI, int typeCodeJ>
struct SafeImp
{
    static inline bool Less( I x, J y )
    {
        return x < y;
    }
};

template <typename I, typename J>
struct SafeImp<I, J, 1, 0>
{
    static inline bool Less( I x, J y )
    {
        return (x < 0) || (static_cast<typename ToUnsigned<I>::Type>(x) < y);
    }
};

template <typename I, typename J>
struct SafeImp<I, J, 0, 1>
{
    static inline bool Less( I x, J y )
    {
        return (y >= 0) && (x < static_cast<typename ToUnsigned<J>::Type>(y));
    }
};

Finally we can write our template function, which just hands off to the template class.

template <typename I, typename J>
inline bool SafeLess( I x, J y )
{
    return SafeImp<I, J,
        NumType<I>::Code, NumType<J>::Code>::Less( x, y );
}

Of course, one could add other methods to SafeImp to implement safe versions of other comparisons such as less than or equal to.

Copyright ©2013 James W. Walker