gsl::narrow-Implementierung verstehen

Lesezeit: 4 Minuten

Benutzer-Avatar
Bolov

Das C++ Core-Richtlinien hat ein narrow Cast, der wirft, wenn der Cast den Wert ändert. Mit Blick auf die Microsoft-Implementierung der Bibliothek:

// 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>
        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;
}

Das zweite verstehe ich nicht if. Auf welchen Sonderfall wird geprüft und warum nicht static_cast<U>


Zur Vollständigkeit:

narrow_cast ist nur ein 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 ist, was es bewirbt:

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

  • Ich weiß nicht genug, um darauf eine Antwort zu geben, aber vielleicht narrow<unsigned>(-1)? EIN static_cast hin und her würde wahrscheinlich das gleiche Ergebnis liefern (nicht sicher, ob es UB ist oder nicht).

    – Kevin

    17. Oktober 2018 um 21:12 Uhr

  • Es sieht für mich so aus wenn sie nicht die gleiche Vorzeichenhaftigkeit haben und eine negativ ist ... also wirfst du dazwischen ohne Vorzeichen und unterzeichnet dann wird geprüft, ob Schild Informationen gehen verloren?

    – Galik

    17. Oktober 2018 um 21:24 Uhr


  • Ich weiß nicht genau, warum es so geschrieben ist, aber wenn ich nur einen Blick darauf werfe, glaube ich, dass Ihr (richtig? falsch?) "true" zurückgibt, um -0.0f in eine ganzzahlige Null umzuwandeln, während die MS-Implementierung dies wahrscheinlich nicht tut für nicht ganzzahlige Werte kompilieren.

    – Tschu

    17. Oktober 2018 um 21:28 Uhr


Benutzer-Avatar
NathanOliver

Dies prüft auf Überlauf. Schauen wir uns an

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

T wird sein int und U wird sein unsigned int. So

T t = narrow_cast<T>(u);

wird speichern -1 in t. Wenn du das wieder hineinwirfst

if (static_cast<U>

das -1 wird wieder umwandeln std::numeric_limits<unsigned int>::max() also wird die Prüfung bestanden. Dies ist jedoch keine gültige Besetzung std::numeric_limits<unsigned int>::max() überläuft ein int und ist undefiniertes Verhalten. Dann machen wir weiter

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

und da die Vorzeichen nicht gleich sind, werten wir aus

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

welches ist

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

Also werfen wir eine Ausnahme. Wenn wir noch weiter gehen und damit zurück wickeln t zu einer positiven Zahl wird, wird die zweite Prüfung bestanden, aber die erste schlägt seitdem fehl t wäre positiv und das Zurückwerfen auf den Quelltyp ist immer noch derselbe positive Wert, der nicht gleich seinem ursprünglichen Wert ist.

  • Ist die (paraphrasierte) Aussage "int i = narrow_cast<int>(std::numeric_limits<unsigned int>::max()); wird speichern -1 in i" durch den Standard garantiert? Soweit ich weiß, ist es implementierungsdefiniert, wird aber auf fast jede Implementierung zutreffen.

    – John Ilacqua

    26. Oktober 2018 um 0:23 Uhr


  • Nitpicking: Ich glaube nicht, dass die Besetzung wirklich stimmt nicht definiert Verhalten. Ich glaube du meintest nicht spezifiziert Verhalten. Arithmetische Ganzzahlüberläufe mit Vorzeichen sind ein undefiniertes Verhalten, aber eine Umwandlung eines nicht darstellbaren Werts ist einfach nicht spezifiziert. Es ist relevant, denn wenn es tatsächlich ein undefiniertes Verhalten wäre, würde der Optimierer die Tests wahrscheinlich vollständig eliminieren.

    – Adrian McCarthy

    18. Juni 2021 um 23:57 Uhr

Benutzer-Avatar
Deduplikator

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

Die obige Überprüfung dient dazu, sicherzustellen, dass unterschiedliche Vorzeichen uns nicht in die Irre führen.

Der erste Teil prüft, ob es überhaupt ein Problem sein könnte, und dient der Optimierung, also kommen wir zur Sache.

Als Beispiel nehmen UINT_MAX (der Größte unsigned int es gibt), und werfen Sie es auf signed.

Vorausgesetzt INT_MAX == UINT_MAX / 2 (welches ist sehr wahrscheinlich, wenn auch nicht ganz durch den Standard garantiert), wird das Ergebnis sein (signed)-1oder nur -1eine negative Zahl.

Während das Zurücksetzen zum ursprünglichen Wert führt, besteht es die erste Prüfung, es ist selbst nicht derselbe Wert, und diese Prüfung fängt den Fehler ab.

1012590cookie-checkgsl::narrow-Implementierung verstehen

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy