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.
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
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/
Is boost an option? If so, try boost::numeric_cast<>. It appears to provide the characteristics you're looking for.
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); }
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.
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.
consider Safe Numerics at 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.
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;
}
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);
来源:https://stackoverflow.com/questions/998571/c-template-for-safe-integer-casts