【问题标题】:Understanding gsl::narrow implementation理解 gsl::narrow 实现
【发布时间】:2019-03-22 15:36:27
【问题描述】:

C++ Core Guidelines 有一个 narrow 转换,如果转换更改了值,则会抛出该转换。看图书馆的microsoft implementation

// narrow() : a checked version of narrow_cast() that throws if the cast changed the value
template <class T, class U>
T narrow(U u) noexcept(false)
{
    T t = narrow_cast<T>(u);
    if (static_cast<U>(t) != u)
        gsl::details::throw_exception(narrowing_error());
    if (!details::is_same_signedness<T, U>::value && ((t < T{}) != (u < U{})))  // <-- ???
        gsl::details::throw_exception(narrowing_error());
    return t;
}

我不明白第二个if。它检查什么特殊情况,为什么static_cast&lt;U&gt;(t) != u 不够?


为了完整性:

narrow_cast 只是一个static_cast

// narrow_cast(): a searchable way to do narrowing casts of values
template <class T, class U>
constexpr T narrow_cast(U&& u) noexcept
{
    return static_cast<T>(std::forward<U>(u));
}

details::is_same_signdess 是它所宣传的:

template <class T, class U>
struct is_same_signedness
    : public std::integral_constant<bool,
        std::is_signed<T>::value == std::is_signed<U>::value>
{
};

【问题讨论】:

  • 我知道的不够多,无法回答,但也许narrow&lt;unsigned&gt;(-1)static_cast 来回可能会产生相同的结果(不确定它是否是 UB)。
  • 在我看来,如果它们不是相同的签名并且一个是负数...所以你在 unsigned 之间进行转换签名,然后检查签名信息是否丢失?
  • 我不知道为什么它是这样写的,但只是看了一眼,我相信你会(正确?错误?)返回“true”以将 -0.0f 转换为整数零,而 MS 实现可能不会针对非整数值进行编译。

标签: c++ c++11 casting narrowing cpp-core-guidelines


【解决方案1】:

这是检查溢出。来看看

auto foo = narrow<int>(std::numeric_limits<unsigned int>::max())

T 将是 intU 将是 unsigned int。所以

T t = narrow_cast<T>(u);

将在t 中存储-1。当你把它放回去时

if (static_cast<U>(t) != u)

-1 将转换回std::numeric_limits&lt;unsigned int&gt;::max(),因此检查将通过。尽管std::numeric_limits&lt;unsigned int&gt;::max() 溢出int 并且是未定义的行为,但这不是有效的演员表。那么我们继续

if (!details::is_same_signedness<T, U>::value && ((t < T{}) != (u < U{})))

由于符号不同,我们评估

(t < T{}) != (u < U{})

这是

(-1 < 0) != (really_big_number < 0)
==  true != false
==  true

所以我们抛出一个异常。如果我们走得更远并回绕 using 以使 t 变为正数,那么第二次检查将通过,但第一次检查将失败,因为 t 将是正数并且转换回源类型仍然相同不等于原始值的正值。

【讨论】:

  • 标准是否保证(转述的)语句“int i = narrow_cast&lt;int&gt;(std::numeric_limits&lt;unsigned int&gt;::max()); 将在i 中存储-1”?据我所知,它是由实现定义的,但几乎在每个实现中都是如此。
  • 吹毛求疵:我不相信演员阵容是真正的未定义行为。我认为您的意思是 unspecified 行为。算术有符号整数溢出是未定义的行为,但无法表示的值的强制转换只是未指定的。这是相关的,因为如果它实际上是未定义的行为,优化器可能会完全消除测试。
【解决方案2】:
if (!details::is_same_signedness<T, U>::value && ((t < T{}) != (u < U{})))  // <-- ???

上述检查是为了确保不同的签名不会使我们误入歧途。

第一部分检查它是否可能是一个问题,并被包括在内以进行优化,所以让我们进入正题。

UINT_MAX(最大的unsigned int)为例,将其转换为signed

假设INT_MAX == UINT_MAX / 2(这非常可能,但标准不能完全保证),结果将是(signed)-1,或者只是-1,一个负数。

虽然将其转换回原始值,但它通过了第一次检查,但它本身不是相同的值,并且此检查会捕获错误。

【讨论】:

    猜你喜欢
    • 2021-11-24
    • 1970-01-01
    • 2015-11-30
    • 2020-04-03
    • 2021-07-01
    • 2013-02-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多