C++ Template for safe integer casts
Asked Answered
L

11

12

I am trying to write a C++ template function that will throw a runtime exception on integer overflow in casts between different integral types, with different widths, and possible signed/unsigned mismatch. For these purposes I'm not concerned with casting from floating-point types to integral types, nor other object-to-object conversions. I'd like to do this without having to write lots of special case code. This is what I currently have:

template< typename T, typename R > void safe_cast( const T& source, R& result )
{
    // get the maximum safe value of type R
    R rMax = (R) ~0;
    if ( rMax < 0 ) // R is a signed type
    {
        // assume that we're on an 8-bit twos-compliment machine
        rMax = ~( 0x80 << ( ( sizeof( R ) - 1 ) * 8 ) );
    }

    if ( ( source & rMax  ) != source )
    {
        throw new IntegerOverflowException( source );
    }

    result = static_cast<R>( source );
}

Is this correct and efficient?

EDIT: For various reasons stl isn't available, so I can't use std::numeric_limits, and anything from Boost is right out.

Lawrence answered 15/6, 2009 at 21:48 Comment(3)
But you could copy the code you need from numeric_limits into your own templated helper. Assign everything to uint64 (or whatever the maximum allowable size is) and do comparisons within that type.Ky
That might work, but one really needs to be aware of the licensing terms when copying code like this. Besides potentially violating the terms, one could inadvertently "infect" their code, as is the case with the GPL. Make sure both licenses are compatible before doing this sort of thing. The usual "I am not a lawyer" disclaimer applies.Freightage
What are the various reasons you can't use the STL?Flowery
W
5

Have you tried SafeInt? It's a cross platform template that will do integer overflow checks for a variety of integer types. It's available on github

Willena answered 15/6, 2009 at 22:12 Comment(0)
L
13

You can get the minimum and maximum safe values (and a whole lot of other information) for any fundamental type in a much more elegant way using the std::numeric_limits template, e.g. std::numeric_limits<T>::max(). You'll need to include <limits>.

Reference: http://www.cplusplus.com/reference/std/limits/numeric_limits/

Letter answered 15/6, 2009 at 21:51 Comment(2)
this edit 7 years later made me change my upvote to a downvote. Because the presented example added by @jpo38 does not work. Example pair: From=int, To=unsigned. source==-1.Tami
Actually, I saw that and fixed that in my code since I edited this post. But forgot to update the edit...sorry. I now throw if static_cast<From>(static_cast<To>( source ) ) != source (basically, if some information were lost by the cast...this works great. Somehow similar to what Tim proposes below. No reference to max/min wish fails when moving from signed to unsigned and the way around.Simpkins
F
13

Is boost an option? If so, try boost::numeric_cast<>. It appears to provide the characteristics you're looking for.

Freightage answered 15/6, 2009 at 22:10 Comment(0)
T
8

I think these work now, regardless of whether you use two's complement or not. Please test extensively before you use it. They give the following results. Each line gives one assertion failure (just change them into exceptions as you please)

/* unsigned -> signed, overflow */
safe_cast<short>(UINT_MAX);

/* unsigned -> unsigned, overflow */
safe_cast<unsigned char>(ULONG_MAX);

/* signed -> unsigned, overflow */
safe_cast<unsigned long>(-1);

/* signed -> signed, overflow */
safe_cast<signed char>(INT_MAX);

/* always works (no check done) */
safe_cast<long>(INT_MAX);

// giving these assertion failures results
(type)f <= (type)is_signed<To>::v_max
f <= (To)-1
f >= 0
f >= is_signed<To>::v_min && f <= is_signed<To>::v_max

Implementation. First some utilities to check for integer ranks (types with higher ranks will be able to contain values of types with lower rank, given the same sign. And some promotion tools, to be able to figure out a common, safe type (this will never yield a signed type if an unsigned type is involved, if the signed type won't be able to store all values of the unsigned one).

/* ranks */
template<typename> struct int_rank;
#define RANK(T, I) template<> struct int_rank<T> \
    { static int const value = I; }

RANK(char, 1); RANK(unsigned char, 1); RANK(signed char, 1); 
RANK(short, 2); RANK(unsigned short, 2);
RANK(int, 3); RANK(unsigned int, 3);
RANK(long, 4); RANK(unsigned long, 4);
#undef RANK

/* usual arith. conversions for ints (pre-condition: A, B differ) */
template<int> struct uac_at;
template<> struct uac_at<1> { typedef int type; };
template<> struct uac_at<2> { typedef unsigned int type; };
template<> struct uac_at<3> { typedef long type; };
template<> struct uac_at<4> { typedef unsigned long type; };

template<typename A, typename B>
struct uac_type { 
    static char (&f(int))[1];
    static char (&f(unsigned int))[2];
    static char (&f(long))[3];
    static char (&f(unsigned long))[4];
    typedef typename uac_at<sizeof f(0 ? A() : B())>::type type; 
};

/* signed games */
template<typename> struct is_signed { static bool const value = false; };
#define SG(X, TT) template<> struct is_signed<X> { \
    static bool const value = true;                \
    static X const v_min = TT##_MIN;               \
    static X const v_max = TT##_MAX;               \
}

SG(signed char, SCHAR); 
SG(short, SHRT); 
SG(int, INT); 
SG(long, LONG); 
#undef SG

template<> struct is_signed<char> { 
    static bool const value = (CHAR_MIN < 0); 
    static char const v_min = CHAR_MIN; // just in case it's signed...
    static char const v_max = CHAR_MAX;
};

The conversion templates make use of them, to figure out for each case when what needs to be done or not done.

template<typename To, typename From, 
         bool to_signed = is_signed<To>::value, 
         bool from_signed = is_signed<From>::value,
         bool rank_fine = (int_rank<To>::value >= int_rank<From>::value)>
struct do_conv;

/* these conversions never overflow, like int -> int, 
 * or  int -> long. */
template<typename To, typename From, bool Sign>
struct do_conv<To, From, Sign, Sign, true> {
    static To call(From f) {
        return (To)f; 
    }
};

template<typename To, typename From>
struct do_conv<To, From, false, false, false> {
    static To call(From f) {
        assert(f <= (To)-1);
        return (To)f;
    }
};

template<typename To, typename From>
struct do_conv<To, From, false, true, true> {
    typedef typename uac_type<To, From>::type type;
    static To call(From f) {
        /* no need to check whether To's positive range will
         * store From's positive range: Because the rank is
         * fine, and To is unsigned. 
         * Fixes GCC warning "comparison is always true" */
        assert(f >= 0);
        return (To)f;
    }
};

template<typename To, typename From>
struct do_conv<To, From, false, true, false> {
    typedef typename uac_type<To, From>::type type;
    static To call(From f) {
        assert(f >= 0 && (type)f <= (type)(To)-1);
        return (To)f;
    }
};

template<typename To, typename From, bool Rank>
struct do_conv<To, From, true, false, Rank> {
    typedef typename uac_type<To, From>::type type;
    static To call(From f) {
        assert((type)f <= (type)is_signed<To>::v_max);
        return (To)f;
    }
};

template<typename To, typename From>
struct do_conv<To, From, true, true, false> {
    static To call(From f) {
        assert(f >= is_signed<To>::v_min && f <= is_signed<To>::v_max);
        return (To)f;
    }
};

template<typename To, typename From>
To safe_cast(From f) { return do_conv<To, From>::call(f); }
Tami answered 15/6, 2009 at 23:39 Comment(0)
W
5

Have you tried SafeInt? It's a cross platform template that will do integer overflow checks for a variety of integer types. It's available on github

Willena answered 15/6, 2009 at 22:12 Comment(0)
B
4

How about:

template< typename T, typename R > void safe_cast( const T& source, R& result )
{
    R temp = static_cast<R>( source );
    if (static_cast<T> (temp) != source
        || ( temp < 0 && source > 0)
        || ( temp > 0 && source < 0))
    {
        throw IntegerOverflowException( source );
    }
    result = temp;
}

Then you're just checking if the casting worked. Make sure you get back what you started with, and that the sign didn't flip.

EDIT: Since the comment below got messed up, here it is, formatted:

int myint (-1);
safe_cast( myint, mychar );
safe_cast( mychar, myuchar ); // Exception is thrown here
safe_cast( myuchar, myint );

The cast from int to char works fine. The cast from char to unsigned char throws an exception (as it should). I don't see a problem here.

Bot answered 15/6, 2009 at 23:51 Comment(2)
o, The compiler will spit warnings when one of the two types is unsigned and the other is not (comparison is always false due to limited range of data type in g++) when comparing with 0, but it will not throw and data will be lost: If you use your cast from -1 (int) to -1 (char) to 0xFF (unsigned char) back to int you will not get -1. The cast is not 'safe' as the values get changed in the way.Schalles
ah, I had tested with a different compiler. The warnings refer to "temp < 0" when temp is unsigned, which is ok. It shouldn't throw at this point, and no data is lost. I tested what you suggest, i.e.: int myint (-1); safe_cast( myint, mychar ); safe_cast( mychar, myuchar ); // Exception is thrown here safe_cast( myuchar, myint ); The cast from int to char works fine. The cast from char to unsigned char throws an exception (as it should). I don't see a problem here.Bot
L
2

Consider Safe Numerics at https://www.boost.org/doc/libs/1_71_0/libs/safe_numerics/doc/html/index.html http://rrsd.com/blincubator.com/bi_library/safe-numerics

This library provides drop-in replacements for all C primitive integer types. C operations which result erroneous results - including casting are trapped when detected.

Louielouis answered 6/11, 2014 at 21:26 Comment(0)
B
2

It's been over a decade since this question was posted, and I wanted a solution that was self contained and used modern C++ (std::optional, constexpr, type_traits). Here is what I wrote:

/// Cast integer of type "From" to integer of type "To", as long as it fits. If it doesn't
/// fit, return std::nullopt.
template<typename To, typename From>
constexpr std::optional<To> IntegerCast(From from) {
    static_assert(std::is_integral_v<From>, "IntegerCast only supports integers");
    static_assert(std::is_integral_v<To>, "IntegerCast only supports integers");
    static_assert(!std::is_same_v<To, bool>, "IntegerCast only supports integers");
    static_assert(!std::is_same_v<From, bool>, "IntegerCast only supports integers");

    constexpr bool fromSigned = std::is_signed_v<From>;
    constexpr bool toSigned = std::is_signed_v<To>;
    constexpr bool bothSigned = fromSigned && toSigned;
    constexpr bool bothUnsigned = !fromSigned && !toSigned;

    constexpr From fromMax = std::numeric_limits<From>::max();
    constexpr From fromMin = std::numeric_limits<From>::min();
    constexpr To toMax = std::numeric_limits<To>::max();
    constexpr To toMin = std::numeric_limits<To>::min();

    if constexpr (bothUnsigned) {
        using Widen = std::conditional_t<(sizeof(From) > sizeof(To)), From, To>;
        if (from > Widen(toMax)) {
            return std::nullopt;
        } else {
            return To(from);
        }
    } else if constexpr (bothSigned) {
        using Widen = std::conditional_t<(sizeof(From) > sizeof(To)), From, To>;
        if (from > Widen(toMax)) {
            return std::nullopt;
        } else if (from < Widen(toMin)) {
            return std::nullopt;
        } else {
            return To(from);
        }
    } else if constexpr (fromSigned && !toSigned) {
        using Widen =
                std::make_unsigned_t<std::conditional_t<(sizeof(From) > sizeof(To)), From, To>>;
        if (from < 0) {
            return std::nullopt;
        } else if (from > Widen(toMax)) {
            return std::nullopt;
        } else {
            return To(from);
        }
    } else if constexpr (!fromSigned && toSigned) {
        using Widen =
                std::make_unsigned_t<std::conditional_t<(sizeof(From) > sizeof(To)), From, To>>;
        if (from > Widen(toMax)) {
            return std::nullopt;
        } else {
            return To(from);
        }
    }
}

It comes with a test suite in GoogleTest

TEST(IntegerCast, Basics) {
    constexpr uint64_t large64 = 10000000000000000000ull;
    static_assert(IntegerCast<uint8_t>(large64) == std::nullopt);
    static_assert(IntegerCast<uint16_t>(large64) == std::nullopt);
    static_assert(IntegerCast<uint32_t>(large64) == std::nullopt);
    static_assert(IntegerCast<uint64_t>(large64) == 10000000000000000000ull);
    static_assert(IntegerCast<int8_t>(large64) == std::nullopt);
    static_assert(IntegerCast<int16_t>(large64) == std::nullopt);
    static_assert(IntegerCast<int32_t>(large64) == std::nullopt);
    static_assert(IntegerCast<int64_t>(large64) == std::nullopt);

    constexpr int64_t largeNegative64 = -5000000000000000000;
    static_assert(IntegerCast<uint8_t>(largeNegative64) == std::nullopt);
    static_assert(IntegerCast<uint16_t>(largeNegative64) == std::nullopt);
    static_assert(IntegerCast<uint32_t>(largeNegative64) == std::nullopt);
    static_assert(IntegerCast<uint64_t>(largeNegative64) == std::nullopt);
    static_assert(IntegerCast<int8_t>(largeNegative64) == std::nullopt);
    static_assert(IntegerCast<int16_t>(largeNegative64) == std::nullopt);
    static_assert(IntegerCast<int32_t>(largeNegative64) == std::nullopt);
    static_assert(IntegerCast<int64_t>(largeNegative64) == -5000000000000000000);

    constexpr uint64_t small64 = 1;
    static_assert(IntegerCast<uint8_t>(small64) == 1);
    static_assert(IntegerCast<uint16_t>(small64) == 1);
    static_assert(IntegerCast<uint32_t>(small64) == 1);
    static_assert(IntegerCast<uint64_t>(small64) == 1);
    static_assert(IntegerCast<int8_t>(small64) == 1);
    static_assert(IntegerCast<int16_t>(small64) == 1);
    static_assert(IntegerCast<int32_t>(small64) == 1);
    static_assert(IntegerCast<int64_t>(small64) == 1);

    constexpr int64_t smallNegative64 = -1;
    static_assert(IntegerCast<uint8_t>(smallNegative64) == std::nullopt);
    static_assert(IntegerCast<uint16_t>(smallNegative64) == std::nullopt);
    static_assert(IntegerCast<uint32_t>(smallNegative64) == std::nullopt);
    static_assert(IntegerCast<uint64_t>(smallNegative64) == std::nullopt);
    static_assert(IntegerCast<int8_t>(smallNegative64) == -1);
    static_assert(IntegerCast<int16_t>(smallNegative64) == -1);
    static_assert(IntegerCast<int32_t>(smallNegative64) == -1);
    static_assert(IntegerCast<int64_t>(smallNegative64) == -1);
}

TEST(IntegerCast, Boundaries) {
    constexpr uint8_t maxUnsigned8 = 255;
    static_assert(IntegerCast<uint8_t>(maxUnsigned8) == 255);
    static_assert(IntegerCast<uint16_t>(maxUnsigned8) == 255);
    static_assert(IntegerCast<uint32_t>(maxUnsigned8) == 255);
    static_assert(IntegerCast<uint64_t>(maxUnsigned8) == 255);
    static_assert(IntegerCast<int8_t>(maxUnsigned8) == std::nullopt);
    static_assert(IntegerCast<int16_t>(maxUnsigned8) == 255);
    static_assert(IntegerCast<int32_t>(maxUnsigned8) == 255);
    static_assert(IntegerCast<int64_t>(maxUnsigned8) == 255);

    constexpr uint8_t minUnisigned8 = 0;
    static_assert(IntegerCast<uint8_t>(minUnisigned8) == 0);
    static_assert(IntegerCast<uint16_t>(minUnisigned8) == 0);
    static_assert(IntegerCast<uint32_t>(minUnisigned8) == 0);
    static_assert(IntegerCast<uint64_t>(minUnisigned8) == 0);
    static_assert(IntegerCast<int8_t>(minUnisigned8) == 0);
    static_assert(IntegerCast<int16_t>(minUnisigned8) == 0);
    static_assert(IntegerCast<int32_t>(minUnisigned8) == 0);
    static_assert(IntegerCast<int64_t>(minUnisigned8) == 0);

    constexpr int8_t maxSigned8 = 127;
    static_assert(IntegerCast<uint8_t>(maxSigned8) == 127);
    static_assert(IntegerCast<uint16_t>(maxSigned8) == 127);
    static_assert(IntegerCast<uint32_t>(maxSigned8) == 127);
    static_assert(IntegerCast<uint64_t>(maxSigned8) == 127);
    static_assert(IntegerCast<int8_t>(maxSigned8) == 127);
    static_assert(IntegerCast<int16_t>(maxSigned8) == 127);
    static_assert(IntegerCast<int32_t>(maxSigned8) == 127);
    static_assert(IntegerCast<int64_t>(maxSigned8) == 127);

    constexpr int8_t minSigned8 = -128;
    static_assert(IntegerCast<uint8_t>(minSigned8) == std::nullopt);
    static_assert(IntegerCast<uint16_t>(minSigned8) == std::nullopt);
    static_assert(IntegerCast<uint32_t>(minSigned8) == std::nullopt);
    static_assert(IntegerCast<uint64_t>(minSigned8) == std::nullopt);
    static_assert(IntegerCast<int8_t>(minSigned8) == -128);
    static_assert(IntegerCast<int16_t>(minSigned8) == -128);
    static_assert(IntegerCast<int32_t>(minSigned8) == -128);
    static_assert(IntegerCast<int64_t>(minSigned8) == -128);
}

TEST(IntegerCast, SameSizeDifferentSign) {
    constexpr uint8_t above = 200;
    static_assert(IntegerCast<int8_t>(above) == std::nullopt);

    constexpr uint8_t withinUnsigned = 100;
    static_assert(IntegerCast<int8_t>(withinUnsigned) == 100);

    constexpr int8_t withinSigned = 100;
    static_assert(IntegerCast<uint8_t>(withinSigned) == 100);

    constexpr int8_t below = -100;
    static_assert(IntegerCast<uint8_t>(below) == std::nullopt);
}
Breastwork answered 2/5, 2020 at 0:36 Comment(0)
A
1

Am I correct in assuming that in the case that R is signed you are trying to fill rMax with all 1s except for the last bit? If that's the case, then you should have 0x80 (1000 0000) instead of 0x10 (0001 0000).

Also it doesn't look like your function supports negative numbers for the source.

Edit:

Here is a slightly edited version that I've tested for converting from ints to chars:

template< typename T, typename R >
void safe_cast( const T& source, R& result )
{
    // get the maximum safe value of type R
    R rMax = (R) ~0;
    if ( rMax < 0 ) // R is a signed type
    {
        // assume that we're on an 8-bit twos-compliment machine
    rMax = ( 0x80 << ( ( sizeof( R ) - 1 ) * 8 ) );
    if(source >= 0)
        rMax = ~rMax;
    }

    if ( (source >= 0 && ( source & rMax  ) != source) || (source < 0 && (source & rMax) != rMax) )
    {
        throw new IntegerOverflowException( source );
    }

    result = static_cast<R>( source );
}

Edit: fixed error.

Abbatial answered 15/6, 2009 at 22:15 Comment(0)
B
0

I have a single header at sweet.hpp called conv.hpp. It will test the bounds for all integer types and also allows to and from string casts for integer.

short a = to<short>(1337);
std::string b = to<std::string>(a);
long c = to<long>(b);
Benitabenites answered 18/2, 2014 at 10:6 Comment(0)
C
0

This is quite similar to the answer posted by Drew in some ways, but you might find it easier to understand.

template<typename to_t, typename from_t>
static to_t safeIntegerConvert(from_t value)
{
    // This function is composed of two concepts
    // There is a compile time component, which uses static_assert
    // and constexpr to make decisions at compile time, and a run time
    // component, which uses normal branching if statements.
    // As much decision making is placed into the compile time
    // branches. Things which are known at compile time, such as the
    // widths of the types, can be used to make compile time decisions.
    // Things which are not known until runtime, such as the exact
    // value of `from_t value`, are compiled into runtime code.

    // First check that the template types are integers - things we expect
    // this function to work with
    static_assert(std::is_integral_v<from_t>, "Can only test integer types!");
    static_assert(std::is_integral_v<to_t>, "Can only test integer types!");
    static_assert(!std::is_same_v<bool, from_t>, "Can only test integer types!");
    static_assert(!std::is_same_v<bool, to_t>, "Can only test integer types!");

    // An unsigned to signed conversion. The width of the return type
    // is 1 bit shorter due to the sign bit
    if constexpr (std::is_signed_v<to_t> && !std::is_signed_v<from_t>)
    {
        // Check if value will fit into the return type

        constexpr auto valueTypeNumberOfBits = 8 * sizeof(from_t);
        constexpr auto returnTypeNumberOfBits = 8 * sizeof(to_t) - 1;

        if constexpr (returnTypeNumberOfBits > valueTypeNumberOfBits)
        {
            return static_cast<to_t>(value);
        }
        else
        {
            // Now switch to runtime logic and count the number of
            // bits required to store value

            // __builtin_clz is undefined for 0
            if(value == 0) { return 0 };

            const auto numberOfBitsRequired = valueTypeNumberOfBits - __builtin_clz(value);

            if(numberOfBitsRequired <= returnTypeNumberOfBits)
            {
                return static_cast<to_t>(value);
            }
            else
            {
                throw SomeExceptionGoesHere;
            }
        }
    }
    // A signed to unsigned conversion. The width of the return type
    // is 1 bit longer due to the missing sign bit
    else if constexpr (!std::is_signed_v<to_t> && std::is_signed_v<from_t>)
    {
        // Check if value will fit into the return type

        constexpr auto valueTypeNumberOfBits = 8 * sizeof(from_t) - 1;
        constexpr auto returnTypeNumberOfBits = 8 * sizeof(to_t);

        if constexpr (returnTypeNumberOfBits > valueTypeNumberOfBits)
        {
            return static_cast<to_t>(value);
        }
        else
        {
            // Now switch to runtime logic and count the number of
            // bits required to store value

            // __builtin_clz is undefined for 0
            if(value == 0) { return 0 };

            // Since __builtin_clz only works for unsigned types, we need to make value non-negative
            constexpr from_t bitmask = ~(static_cast<from_t>(1) << (sizeof(from_t) - 1));
            // bitmask & value == abs(value)
            const auto numberOfBitsRequired = valueTypeNumberOfBits - __builtin_clz(bitmask & value);

            if(numberOfBitsRequired <= returnTypeNumberOfBits)
            {
                return static_cast<to_t>(value);
            }
            else
            {
                throw SomeExceptionGoesHere;
            }
        }
    }
    // A signed to signed conversion or an unsigned to unsigned
    // conversion. Do not need to take into account the difference
    // in width due to the sign bit
    else
    {
        // Check if value will fit into the return type

        constexpr auto valueTypeNumberOfBits = 8 * sizeof(from_t);
        constexpr auto returnTypeNumberOfBits = 8 * sizeof(to_t);

        if constexpr (returnTypeNumberOfBits > valueTypeNumberOfBits)
        {
            return static_cast<to_t>(value);
        }
        else
        {
            // Now switch to runtime logic and count the number of
            // bits required to store value
            
            // __builtin_clz is undefined for 0
            if(value == 0) { return 0 };

            // Since __builtin_clz only works for unsigned types, we need to make value non-negative
            constexpr from_t bitmask = ~(static_cast<from_t>(1) << (sizeof(from_t) - 1));
            // bitmask & value == abs(value)
            const auto numberOfBitsRequired = valueTypeNumberOfBits - __builtin_clz(bitmask & value);

            if(numberOfBitsRequired <= returnTypeNumberOfBits)
            {
                return static_cast<to_t>(value);
            }
            else
            {
                throw SomeExceptionGoesHere;
            }
        }
    }
}
Chekhov answered 14/9, 2022 at 17:44 Comment(0)
H
-2

I must be missing something, but isn't this what you want?:

// using a more cast-like prototype, if I may:
template<class to, class from> inline
to safe_cast(from f)
{
   to t = static_cast<to>(f);
   if ( t != f ) throw whatever; // no new!
   return t;
}
Helpmate answered 16/6, 2009 at 4:58 Comment(6)
No, if you use your cast from -1 (int) to -1 (char) to 0xFF (unsigned char) back to int you will not get -1. The cast is not 'safe' as the values get changed in the way.Schalles
Hi Dribeas, I am sorry, I am not sure what you are saying. . safe_cast<char>(int(-1)) doesn't overflow, and returns fine . safe_cast<unsigned char>(char(-1)) changes sign (and value), and throws. hence the correct behavior. or, what are you saying?Helpmate
assuming char is signed, safe_cast<unsigned char>(char(-1)) will set t to UCHAR_MAX (probably 255). then if(t != f) will promote char to int, yielding to -1 and unsigned char to int, yielding 255, thus they are not equal. BUT doing safe_cast<unsigned int>(-1), it will set t to UINT_MAX, then the if will not promote anything, and convert the int to unsigned int (UAC), thus yielding UINT_MAX again, and wrongly thinking the cast succeeded.Tami
I find that hard to believe; does C++ actually differ from C on this point? C99 makes perfectly clear that "When a value with integer type is converted to another integer type ... if the value can be represented by the new type, it is unchanged." Thus--in C--converting UCHAR_MAX to int in your example gives 255.Greenroom
@stephentyrone, the same happens in my example. t is UCHAR_MAX, and when promoted to int it will be 255. What conversion do you refer to in particular?Tami
@litb: sorry, I misunderstood your earlier comment. Yes, I believe that his cast will fail for exactly the reason you gave.Greenroom

© 2022 - 2024 — McMap. All rights reserved.