Понимание gsl:: узкой реализации
Основные принципы C++ имеют narrow
приведение, которое бросает, если приведение изменяет значение. Рассматривая реализацию библиотеки Microsoft:
// 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>
{
};
2 ответа
Это проверка на переполнение. Давайте посмотрим на
auto foo = narrow<int>(std::numeric_limits<unsigned int>::max())
T
будет int
а также U
будет unsigned int
, Так
T t = narrow_cast<T>(u);
даст магазин -1
в t
, Когда вы бросаете это обратно в
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
Поэтому мы бросаем исключение. Если мы пойдем еще дальше и вернемся назад, используя так, чтобы 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
отрицательное число.
В то время как приведение обратно приведет к исходному значению, таким образом, оно проходит первую проверку, само по себе оно не является тем же значением, и эта проверка ловит ошибку.