我正在尝试编写一个C ++模板函数,它将在不同整数类型之间的强制转换中抛出整数溢出的运行时异常,具有不同的宽度,以及可能的有符号/无符号不匹配。出于这些目的,我不关心从浮点类型转换为整数类型,也不关心其他对象到对象的转换。我想这样做而不必写很多特殊的案例代码。这就是我目前所拥有的:
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 );
}
这是正确有效的吗?
编辑:由于各种原因,stl不可用,所以我不能使用std :: numeric_limits,Boost的任何东西都可以用完。
答案 0 :(得分:12)
您可以使用std::numeric_limits
模板以更优雅的方式获取任何基本类型的最小和最大安全值(以及大量其他信息),例如std::numeric_limits<T>::max()
。您需要添加<limits>
。
参考:http://www.cplusplus.com/reference/std/limits/numeric_limits/
答案 1 :(得分:11)
提升是一种选择吗?如果是,请尝试boost::numeric_cast<>。它似乎提供了您正在寻找的特征。
答案 2 :(得分:7)
我认为现在这些都有效,无论你是否使用两个补码。请在使用前进行广泛测试。他们给出了以下结果。每一行都会导致一个断言失败(只需将它们更改为异常)
/* 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
实现。首先检查整数排名的一些实用程序(排名较高的类型将能够包含具有较低排名的类型的值,给定相同的符号。以及一些促销工具,以便能够找出一个常见的,安全的类型(这将永远不会如果涉及无符号类型,则生成有符号类型,如果有符号类型将无法存储无符号类型的所有值。)
/* 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;
};
转换模板利用它们来确定每种情况何时需要完成或不完成。
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); }
答案 3 :(得分:5)
你试过SafeInt吗?它是一个跨平台模板,可以对各种整数类型进行整数溢出检查。它可以在github上找到
答案 4 :(得分:3)
怎么样:
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;
}
然后你只是检查铸件是否有效。确保你找回你的开始,并且标志没有翻转。
编辑: 由于以下评论搞砸了,这里格式化了:
int myint (-1);
safe_cast( myint, mychar );
safe_cast( mychar, myuchar ); // Exception is thrown here
safe_cast( myuchar, myint );
从int到char的转换工作正常。从char转换为unsigned char会抛出异常(应该如此)。我这里没有看到问题。
答案 5 :(得分:1)
我是否正确假设在R签名的情况下你试图用除了最后一位之外的所有1填充rMax?如果是这种情况,那么你应该有0x80(1000 0000)而不是0x10(0001 0000)。
此外,您的功能看起来不支持来源的负数。
编辑:
这是一个稍微编辑过的版本,我已经测试过从ints转换为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 );
}
编辑:修正错误。
答案 6 :(得分:1)
在http://rrsd.com/blincubator.com/bi_library/safe-numerics
处考虑安全数字此库提供所有C原始整数类型的插入替换。导致错误结果的C操作 - 包括铸造在被检测时被捕获。
答案 7 :(得分:1)
自发布此问题以来已有十多年了,我想要一个自包含并使用现代C ++(std::optional
,constexpr
,type_traits
)的解决方案。这是我写的:
/// 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);
}
}
}
它带有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);
}
答案 8 :(得分:0)
我一定错过了什么,但这不是你想要的吗?:
// 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;
}
答案 9 :(得分:0)
我在sweet.hpp有一个名为 conv.hpp 的标题。它将测试所有整数类型的边界,并允许进行整数的字符串强制转换。
short a = to<short>(1337);
std::string b = to<std::string>(a);
long c = to<long>(b);