Boost logo

Boost :

From: Andreas Grosam (agrosam_at_[hidden])
Date: 2002-08-09 14:03:02


Hi All,

The program below shows an issue with the boost::numeric_cast operator.

Platform: Mac OS 9
Compiler: Metrowerks
Boost: v1.27, v1.28

There is an attempt to cast an unsigned type to its corresponding signed
type. Example:

unsigend long ul = ...;
signed long x = numeric_cast<signed long>(ul);

In the example the argument is set to numeric_limits<UnsignedT>::max().
Since the result is of a signed type, it can't hold this value and IMO a
bad_numeric_cast should be thrown in this case - I guess so.

Nevertheless, the program compiles and runs successfully, without throwing a
boost::bad_numeric_cast - which was expected.

I would like to know, if any other has already encountered this issue - and
whether it is already known as a bug.
I do not know, how other compilers do behave in this case - so I would appreciate
it if some others could investigate this issue on his prefered compiler, too.

A more detailed description follows.
Thanks in advance.

Here is the program:

#include <iostream>
#include <limits>
#include <boost/cast.hpp>

int main()
{
        using namespace std;

        try {
                signed char c = boost::numeric_cast<signed char>(numeric_limits<unsigned char>::max());
                cout << (int)c << endl;

                signed int x = boost::numeric_cast<signed int>(numeric_limits<unsigned int>::max());
                cout << x << endl;
        }
        catch (exception& ex)
        {
                cout << ex.what() << endl;
        
                return -1;
        }
        
        return 0;
}

%OUTPUT:

-1
-1

%END OUTPUT

Discussion:

In this example - with the Metrowerks compiler-, the numeric cast is compiled as:

    template<typename Target, typename Source>
    inline Target numeric_cast(Source arg)
    {
        typedef std::numeric_limits<Source> arg_traits;
        typedef detail::fixed_numeric_limits<Target> result_traits;
 
        const bool arg_is_signed = arg_traits::is_signed;
        const bool result_is_signed = result_traits::is_signed;
        const bool same_sign = arg_is_signed == result_is_signed;
       
        if (less_than_type_min<arg_is_signed, result_is_signed>::check(arg, result_traits::min())
            || greater_than_type_max<same_sign, arg_is_signed>::check(arg, result_traits::max())
            )
        {
            throw bad_numeric_cast();
        }
        return static_cast<Target>(arg);
    }

It turned out that the functor greater_than_type_max does not
evaluate the expected result returned by check().
If we take a closer look, greater_than_type_max has been compiled as:

    struct greater_than_type_max<false, false>
    {
        // What does the standard say about this? I think it's right, and it
        // will work with every compiler I know of.
        template <class X, class Y>
        static inline bool check(X x, Y)
            { return static_cast<X>(static_cast<Y>(x)) != x; }
    };

That is, check() is compiled as:

        bool check(X x, Y) { return static_cast<X>(static_cast<Y>(x)) != x; }
        
(where x is the argument)
        
check() can be written as:
        
        signedT r1 = static_cast<signedT>(x);
        unsigendT r2 = static_cast<unsignedT>(r1);
        return r2 != x;
                
The Metrowerks compiler does not change any bit when making a static_cast for an
unsigend T to an signed T (at least for char, int, long, short, etc).
So the static_cast is essentially a reinterpret_cast.
Thus we can reduce the function to:

        r2 = x;
        return r2 != x;
        
or
        return false;

That means, the greater_than_type_max::check() returns allways false, regardless
of the real values and whether an overflow occurs and the bad_numeric_cast will never
be thrown.

Note: the boost::numeric_cast function has conditional compiler flags.
It looks like that the second version would compile such, that it
then would evaluates the right result:

    template<typename Target, typename Source>
    inline Target numeric_cast(Source arg BOOST_EXPLICIT_DEFAULT_TARGET)
    {
        // typedefs abbreviating respective trait classes
        typedef std::numeric_limits<Source> arg_traits;
        typedef detail::fixed_numeric_limits<Target> result_traits;
        if ((arg < 0 && !result_traits::is_signed) // loss of negative range
             || (arg_traits::is_signed && arg < result_traits::min()) // underflow
             || arg > result_traits::max()) // overflow

        {
            throw bad_numeric_cast();
        }
        return static_cast<Target>(arg);
        }

This looks more reasonable to me.

Regards

Andreas Grosam


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk