了解 gsl::narrow 实施
Understanding gsl::narrow implementation
图书馆的C++ Core Guidelines has a narrow
cast that throws if the cast changes the value. Looking at the 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<U>(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>
{
};
这是检查溢出。让我们看看
auto foo = narrow<int>(std::numeric_limits<unsigned int>::max())
T
将是 int
,U
将是 unsigned int
。所以
T t = narrow_cast<T>(u);
将在 t
中存储 -1
。当你把它放回去时
if (static_cast<U>(t) != u)
-1
将转换回 std::numeric_limits<unsigned int>::max()
,因此检查将通过。这不是有效的转换,因为 std::numeric_limits<unsigned int>::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
将是正数并转换回源类型仍然是相同的正值,不等于其原始值。
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
,一个负数。
虽然将其转换回原始值,因此它通过了第一次检查,但它本身不是相同的值,并且此检查捕获了错误。
图书馆的C++ Core Guidelines has a narrow
cast that throws if the cast changes the value. Looking at the 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<U>(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>
{
};
这是检查溢出。让我们看看
auto foo = narrow<int>(std::numeric_limits<unsigned int>::max())
T
将是 int
,U
将是 unsigned int
。所以
T t = narrow_cast<T>(u);
将在 t
中存储 -1
。当你把它放回去时
if (static_cast<U>(t) != u)
-1
将转换回 std::numeric_limits<unsigned int>::max()
,因此检查将通过。这不是有效的转换,因为 std::numeric_limits<unsigned int>::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
将是正数并转换回源类型仍然是相同的正值,不等于其原始值。
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
,一个负数。
虽然将其转换回原始值,因此它通过了第一次检查,但它本身不是相同的值,并且此检查捕获了错误。