Понимание 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 отрицательное число.

В то время как приведение обратно приведет к исходному значению, таким образом, оно проходит первую проверку, само по себе оно не является тем же значением, и эта проверка ловит ошибку.

Другие вопросы по тегам