完全安全的静态/运行时数字转换

时间:2017-04-02 20:02:46

标签: c++ c++11 templates casting

我正在寻找一个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?

这是基于一些广泛的假设,所以我也很高兴听到我在这里遗漏了什么(误报,优化等)。

2 个答案:

答案 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来检测这种转换是否合法,如果是,则返回真实类型。

Live example

缩小转化的标准定义可能与您想要的完全匹配,也可能不完全匹配。但这是一个很好的起点。