定义明确的窄型铸造

Well defined narrowing cast

本文关键字:窄型 定义      更新时间:2023-10-16

我正试图提出一种缩小强制转换(一种通用解决方案),它可以优雅地忽略丢失的数据。在Visual Studio中,丢失数据的缩小强制转换会触发"运行时检查失败#1"。我不想关闭它,相反,我正在尝试实现一个narrow_cast,它可以优雅地缩小强制转换范围,并且不会触发运行时检查。

Visual Studio建议:

char c = (i & 0xFF);

所以我从这个开始,想出了一个丑陋的解决方案:

template< typename T >
struct narrow_mask
{
  static const T value = T(0xFFFFFFFFFFFFFFFF);
};
template <typename T, typename U>
T narrow_cast(const U& a)
{
  return static_cast<T>(a & narrow_mask<T>::value );
}

虽然它可以工作(VS似乎可以很好地处理常数上的数据丢失),但它既不完整(不支持非整数数据),也不正确(我认为它对有符号值不正确)。

对于更好的解决方案或更好的窄掩码实现,有什么建议吗?

编辑:面对这个问题是VS特定的评论,我检查了标准文档,缩小static_cast的结果似乎取决于实现。因此,这个问题可以更好地表述为创建一个定义良好的(ergo,而不是依赖于实现的)窄型转换。我不太关心结果值的细节,只要它定义良好并取决于类型(而不是return 0)。

这里有一个版本使用了一点C++11。如果你没有访问constexpr的权限,你可以删除它。如果你没有std::make_unsigned的权限,可以实现你自己的。如果你没有std::enable_if,你可以使用Boost(或者自己制作)。它适用于有符号和无符号类型,以及正值和负值更新:更新为使用浮点类型(浮点为积分,反之亦然)。

#include <type_traits>
// From integer type to integer type
template <typename to, typename from>
constexpr typename std::enable_if<std::is_integral<from>::value && std::is_integral<to>::value, to>::type
narrow_cast(const from& value)
{
    return static_cast<to>(value & (static_cast<typename std::make_unsigned<from>::type>(-1)));
}
// For these next 3 versions, you'd be best off locally disabling the compiler warning
// There isn't much magic you can do when floating point types get invovled
// From floating point type to floating point type
template <typename to, typename from>
constexpr typename std::enable_if<std::is_floating_point<from>::value && std::is_floating_point<to>::value, to>::type
narrow_cast(const from& value)
{
    // The best you can do here is a direct cast
    return static_cast<to>(value);
}
// From integer type to floating point type
template <typename to, typename from>
constexpr typename std::enable_if<std::is_integral<from>::value && std::is_floating_point<to>::value, to>::type
narrow_cast(const from& value)
{
    // The best you can do here is a direct cast
    return static_cast<to>(value);
}
// From floating point type to integer type
template <typename to, typename from>
constexpr typename std::enable_if<std::is_floating_point<from>::value && std::is_integral<to>::value, to>::type
narrow_cast(const from& value)
{
    // The best you can do here is a direct cast
    return static_cast<to>(value);
}

使用std::numeric_limits和模运算符。获取目标类型允许的最大值,将其强制转换为源类型,加一,取模,然后强制转换为目标类型。

结果值肯定会在目标类型中表示,即不会有未定义的行为,但我不知道MSVC是否仍会抛出警告。我没有复印件要查。

不过,这并不能保留负数。它可能可以扩展到这样做,但我不确定如何扩展。(这里已经很晚了。)

template< typename to, typename from >
to narrow_cast( from value ) {
    static_assert( std::numeric_limits< to >::max() < std::numeric_limits< from >::max(),
        "narrow_cast used in non-narrowing context" );
    return static_cast< to >( from %
        ( static_cast< from >( std::numeric_limits< to >::max() ) + 1 ) ) );
}

Bjarne的TCPPPL提供了某种窄转换,它在运行时检查某些转换是否丢失信息。文本取自第11.5节:

template<class Target, class Source>
Target narrow_cast(Source v)
{
    auto r = static_cast<Target>(v);
    if (static_cast<Source>(r) != v)
        throw runtime_error("narrow_cast<>() failed");
    return r;
}

基本思想是检查反向转换是否返回原始值;那么我们应该感到满意,因为没有丢失信息。此版本适用于积分/积分转换,因为它们都定义得很好(实现必须很好地定义缩小到有符号类型,引发异常是不符合要求的)。尽管如此,您可能应该使用其他重载来完善此定义,并放入一些SFINAE,因为当目标类型太小时,即使对于无符号类型,在浮点/浮点和积分/浮点转换的情况下,此代码也可能导致UB。

小菜一碟!

在这个例子中,让我们取一个指针(在我的机器中为64位),并将其"转换"为一个字节:

char narrowme (char *p) {
    union {
        char *p;
        char c[8];
    } x;
    x.p = p;
    return x.c[0];  // pick the byte you want!
}

要使用,请简单地调用它;例如,c=窄距(&c2);

如果您愿意,可以使用#define。无论如何,总会涉及到代码。