我正在寻找一个C ++特性/代码,我觉得它非常基础并且需要通用编程:能够判断给定数值类型(整数或浮点数)的某些值是否完全可由另一个给定的数字类型表示。
长话短说,我有许多模板类,它们可以在任意数字类型上运行,并且必须正确地进行互操作。例如,内存流可以使用32位大小类型,但文件流具有64位大小类型,并且在它们之间传递数据可能导致溢出。另一个例子是几何类,一个参数化用于64位浮点数,另一个参数化用于64位整数:在它们之间传递数据可能导致舍入误差(整数不能表示分数)和溢出(整数具有比相同值更大的值范围) -sized float)。
所以我想知道是否应该自己实现,或者解决方案随时可用(不幸的是,我不能使用像Boost这样的重量级库)?
更新:在输入我的问题时,我决定推出自己的实现:
#include <limits>
#include <array>
enum class NumberClass {
UNKNOWN, INTEGER, FLOAT
};
template <typename T> struct NumberClassResolver {
static const NumberClass value = std::numeric_limits<T>::is_integer ? NumberClass::INTEGER : (std::numeric_limits<T>::is_iec559 ? NumberClass::FLOAT : NumberClass::UNKNOWN);
};
template <typename From, typename To> struct StaticIntegerCastCheck {
static const bool value = std::is_same<From, To>::value || (sizeof(From) < sizeof(To)) || ((std::numeric_limits<From>::is_signed == std::numeric_limits<To>::is_signed) && (sizeof(From) <= sizeof(To)));
};
template <typename From, typename To> struct StaticFloatCastCheck {
static const bool value = std::is_same<From, To>::value || (sizeof(From) < sizeof(To)); // NOTE Here we rely on the fact, that floats are IEEE-754 compliant and bigger ones has both bigger significand and exponent
};
template <NumberClass FromClass, NumberClass ToClass, typename From, typename To> struct StaticNumberCastCheckHelper;
template <typename From, typename To> struct StaticNumberCastCheckHelper<NumberClass::INTEGER, NumberClass::INTEGER, From, To> {
static const bool value = StaticIntegerCastCheck<From, To>::value;
};
template <typename From, typename To> struct StaticNumberCastCheckHelper<NumberClass::INTEGER, NumberClass::FLOAT, From, To> {
static const bool value = sizeof(To) > sizeof(From); // NOTE Here we rely on assumption, that sizes are POTs and significand part of a float never takes up less that half of it's size
};
template <typename From, typename To> struct StaticNumberCastCheckHelper<NumberClass::FLOAT, NumberClass::INTEGER, From, To> {
static const bool value = false;
};
template <typename From, typename To> struct StaticNumberCastCheckHelper<NumberClass::FLOAT, NumberClass::FLOAT, From, To> {
static const bool value = StaticFloatCastCheck<From, To>::value;
};
template <typename From, typename To> struct StaticNumberCastCheck {
static const bool value = StaticNumberCastCheckHelper<NumberClassResolver<From>::value, NumberClassResolver<To>::value, From, To>::value;
};
template <bool isFromSigned, bool isToSigned, typename From, typename To> struct RuntimeIntegerCastCheckHelper {
static bool check(From value) {
return (value >= std::numeric_limits<To>::min()) && (value <= std::numeric_limits<To>::max()); // NOTE Compiler must eliminate comparisson to zero for unsigned types
}
};
template <bool isStaticallySafeCastable, NumberClass FromClass, NumberClass ToClass, typename From, typename To> struct RuntimeNumberCastCheckHelper {
static bool check(From value) {
return false;
}
};
template <NumberClass FromClass, NumberClass ToClass, typename From, typename To> struct RuntimeNumberCastCheckHelper<true, FromClass, ToClass, From, To> {
static bool check(From value) {
return true;
}
};
template <typename From, typename To> struct RuntimeNumberCastCheckHelper<false, NumberClass::INTEGER, NumberClass::INTEGER, From, To> {
static bool check(From value) {
return RuntimeIntegerCastCheckHelper<std::numeric_limits<From>::is_signed, std::numeric_limits<To>::is_signed, From, To>::check(value);
}
};
template <typename From, typename To> struct RuntimeNumberCastCheckHelper<false, NumberClass::FLOAT, NumberClass::FLOAT, From, To> {
static bool check(From value) {
To toValue = static_cast<To>(value);
From fromValue = static_cast<From>(toValue);
return value == fromValue;
}
};
template <typename From, typename To> struct RuntimeNumberCastCheckHelper<false, NumberClass::INTEGER, NumberClass::FLOAT, From, To> {
static bool check(From value) {
To toValue = static_cast<To>(value);
From fromValue = static_cast<From>(toValue);
return value == fromValue;
}
};
template <typename From, typename To> struct RuntimeNumberCastCheckHelper<false, NumberClass::FLOAT, NumberClass::INTEGER, From, To> {
static bool check(From value) {
To toValue = static_cast<To>(value);
From fromValue = static_cast<From>(toValue);
return value == fromValue;
}
};
template <typename From, typename To> struct RuntimeNumberCastCheck {
static bool check(From value) {
return RuntimeNumberCastCheckHelper<StaticNumberCastCheck<From, To>::value, NumberClassResolver<From>::value, NumberClassResolver<To>::value, From, To>::check(value);
}
};
预期用途:
StaticNumberCastCheck<uint8_t, float>::value; // can we safely cast all 8-bit unsigned integers to float?
RuntimeNumberCastCheck<float, uint8_t>::check(42); // can we safely cast 42 from float to 8-bit unsigned integer?
这是基于一些广泛的假设,所以我也很高兴听到我在这里遗漏了什么(误报,优化等)。
答案 0 :(得分:1)
此信息可在documentation of the std::numeric_traits
template中找到,它为您提供每个整数或浮点类型的最小值和最大值(以及给定的数字类型是整数还是浮点数。)
通常,整数类型显然无法表达每个浮动值,因此您的首要业务是检查is_exact
。这将平衡整数和浮点类型之间的比较。然后,您可以比较min()
和max()
,以查看给定的数字类型是否涵盖其他数字类型的范围。
答案 1 :(得分:1)
template<class From, class To>
using safe_convert_result_t = decltype(To{std::declval<From>()});
namespace details {
template<class...>struct voider{using type=void;};
template<class...Ts>using void_t=typename voider<Ts...>::type;
template<template<class...>class Z, class, class...Ts>
struct can_apply:std::false_type{};
template<template<class...>class Z, class...Ts>
struct can_apply<Z, void_t<Z<Ts...>>, Ts...>:std::true_type{};
}
template<template<class...>class Z, class...Ts>
using can_apply = details::can_apply<Z, void, Ts...>;
template<class From, class To>
using can_safely_convert = can_apply< safe_convert_result_t, From, To >;
当且仅当从From到To没有缩小转换时,can_safely_convert
应为true。
这使用缩小转换的C ++标准定义。
诀窍是C ++禁止基于{}
的初始化是一个缩小的转换。我们使用SFINAE来检测这种转换是否合法,如果是,则返回真实类型。
缩小转化的标准定义可能与您想要的完全匹配,也可能不完全匹配。但这是一个很好的起点。