C++11 isnan



  • SeppJ schrieb:

    Wieso kein Template: Es gibt drei, und zwar genau drei, Datentypen bei denen isnan Verwendung finden kann. Daher: Überladung. swap kann für alle Datentypen in Betracht kommen, die == können, also auch benutzerdefinierte Typen. Daher Template.

    vorteil: ein paar wenige ms compilezeit gespart
    nachteil: und man muss seine eigenen templates dann für alle anderen typen spezialisieren

    find ich nicht sonderlich toll.


  • Mod

    Was für einen Sinn soll ein Template fur isnan machen? Wie soll ein universeller Code zum Feststellen des Konzepts von NaN aussehen?

    template<typename T> bool isnan(T var)
    {
        volatile T d = var;
        return d != d;
    }
    

    So? Mit welchen Typen soll das sinnvoll gehen? Wenn du deine eigene Fließkommaklasse schreibst, dann überlad eben die Funktion. Für 99.9999999 % aller Klassen die den Operator= und Operator!= kennen ist diese Funktion nutzlos. Höchstwahrscheinlich wäre deine eigene Implementierung auch viel besser, weil sie die interne Repräsentation benutzen kann.

    Überladung ist ein tolles Sprachmittel und hier total angesagt. Du würdest ja auch nicht sin() als Template implementieren, um eigene arithmetische Datentypen benutzen zu können. Oder? Falls doch (bitte nicht!), wo sähest du den Vorteil?



  • Die Referenz ist an dieser Stelle fehlerhaft. Aus C++11:

    The classification/comparison functions behave the same as the C macros with the corresponding names defined in 7.12.3, Classification macros, and 7.12.14, Comparison macros in the C Standard. Each function is overloaded for the three floating-point types, as follows:

    (...)

    bool isnan(float x);

    (...)

    bool isnan(double x);

    (...)

    bool isnan(long double x);

    Die Funktionen isinf, isfinite, isnormal und signbit sowie die Funktionsfamilie isgreater, isless etc. geben ebenfalls bool zurück.

    Ansonsten ist mir unklar, wie sinnvolle Funktionsvorlagen hier arbeiten sollten. Welche wohldefinierte Eigenschaft von NaN willst du denn ausnutzen? Wie erkennt man ohne genaueres Wissen über den konkreten Datentypen Unendlich? Ich sehe auch nicht, welche Nachteile sich durch überladene Funktionen ergeben; der Code, den du oben als fehlerhaft bezeichnest, ist (wenn man die fehlenden Funktionen hinzufügt) gültig.



  • unskilled schrieb:

    1. wieso kein template, was spezialisiert wird? wie bei swap?

    Wozu denn?

    unskilled schrieb:

    class my_number
    {
      /* ... */
    };
    
    int isnan(const my_number& val)
    {
      /* ... */
    
      return false;
    }
    
    template <typename T>
    T my_save_div(T lhs, const T& rhs)
    {
      using std::isnan;
    
      if( isnan(lhs) || isnan(rhs) )
        throw "asd1";
    
      if( rhs == T(0) )
        throw "asd2";
    
      return lhs /= rhs;
    }
    
    int main()
    {
      std::cout << my_save_div(my_number(3), my_number(2)) << std::endl;
      std::cout << my_save_div(3, 2) << std::endl;
      std::cout << my_save_div(3.f, 2.f) << std::endl;
    //usw.
    }
    

    geht jetzt ja nicht so wirklich.

    Was "geht jetzt ja nicht so wirklich" ?! Es sieht doch gut aus. Deine Verwendung von isnan ist unqualifiziert. Von daher ist ADL für Deinen benutzerdefinierten Typ aktiv.

    unskilled schrieb:

    2. wieso int statt bool als rückgabewert?

    C99 Kompatibilität, würde ich mal raten.



  • std::cout << my_save_div(3, 2) << std::endl;
    in my_save_div wird dann aber keine überladung für isnan(int) gefunden.

    Wie soll ein universeller Code zum Feststellen des Konzepts von NaN aussehen?

    das template gibt einfach false zurück und wird für float, double und long double spezialisiert. und für eigene datentypen falls sinnvoll (brüche, ...).

    bb


  • Mod

    unskilled schrieb:

    das template gibt einfach false zurück und wird für float, double und long double spezialisiert. und für eigene datentypen falls sinnvoll (brüche, ...).

    Du beschreibst Überladung. Und zwar schlechte, denn sie gibt keinen Compilerfehler im Falle eines Logikfehlers (falscher Typ) zurück, stattdessen ein schwachsinniges Ergebnis.



  • SeppJ schrieb:

    Du beschreibst Überladung. Und zwar schlechte, denn sie gibt keinen Compilerfehler im Falle eines Logikfehlers (falscher Typ) zurück, stattdessen ein schwachsinniges Ergebnis.

    meiner meinung nach kann es für so etwas keinen falschen typen geben.
    wenn das objekt kein NaN darstellen kann, gibt die funktion eben stumpf false zurück - hat für mich wenig mit einem logikfehler zu tun.


  • Mod

    Und wieso ist es false? Ein Auto ist ganz klar keine Zahl. Wieso gibt isnan für meine Klasse Auto dann false zurück (Übrigens selbst bei meinem obigen Beispielcode, was nur zeigt, dass das Konzept sich nicht allgemein fassen lässt)? Aber der int ist ganz eindeutig eine Zahl, egal welchen Wert er hat. Und auch meine eigene Quaternionenklasse kann nur Zahlen darstellen. Soll ich die jetzt alle spezialisieren für nix? Oder doch lieber alle nicht-arithmetischen Klassen?

    Außerdem habe ich bestimmt nicht gemeint, isnan auf ein Auto oder ein Quaternion anzuwenden, denn ich kenne das Ergebnis schon. Habe also einen Programmierfehler. Ich merke es aber nicht.



  • SeppJ schrieb:

    Außerdem habe ich bestimmt nicht gemeint, isnan auf ein Auto oder ein Quaternion anzuwenden, denn ich kenne das Ergebnis schon. Habe also einen Programmierfehler. Ich merke es aber nicht.

    stimmt - an nicht-arithmetische-typen hab ich gar nicht gedacht...^^ ok, dann leuchtet es fast ein 😉

    also läufts schlussendlich im kostruierten bsp doch auf ein

    namespace detail
    {
      bool isnan(int)
      {
        return false;
      }
    //bool isnan(short) etc.
    }
    
    template <typename T>
    T my_save_div(T lhs, const T& rhs)
    {
      using std::isnan;
      using detail:isnan;
    
      if( isnan(lhs) || isnan(rhs) )
        throw "asd1";
    
      if( rhs == T(0) )
        throw "asd2";
    
      return lhs /= rhs;
    }
    
    int main()
    {
      my_save_div(4,2);
      my_save_div(3,0);
    }
    

    vermutlich kann man da auch mit enable_if und ner numeric-trait-klasse was schöneres bauen.


  • Mod

    Mit traits sieht es evtl. so aus:

    #include <cmath>
    #include <limits>
    #include <utility>
    
    template <typename T, typename = typename std::enable_if<std::numeric_limits<T>::is_specialized && std::numeric_limits<T>::has_quiet_NaN>::type>
    bool my_isnan(const T& v)
    {
        using std::isnan;
        return isnan( v );
    }
    
    template <typename T, bool hack = true, typename = typename std::enable_if<std::numeric_limits<T>::is_specialized && !std::numeric_limits<T>::has_quiet_NaN>::type>
    constexpr bool my_isnan(const T&)
    {
        return true;
    }
    
    template <typename T, typename = typename std::enable_if<std::numeric_limits<T>::is_specialized && std::numeric_limits<T>::has_infinity>::type>
    bool my_isinf(const T& v)
    {
        using std::isinf;
        return isinf( v );
    }
    
    template <typename T, bool hack = true, typename = typename std::enable_if<std::numeric_limits<T>::is_specialized && !std::numeric_limits<T>::has_infinity>::type>
    constexpr bool my_isinf(const T&)
    {
        return false;
    }
    
    template <typename T, typename = typename std::enable_if<std::numeric_limits<T>::is_specialized>::type>
    constexpr bool my_iszero(const T& v)
    {
        return v == T();
    }
    
    template <typename T, typename U>
    auto my_save_div(T&& lhs, U&& rhs) -> decltype( lhs / rhs )
    {
      return my_isnan(lhs) || my_isnan(rhs) ? throw "NaN"
          : my_isinf(lhs) && my_isinf(rhs) ? throw "inf//inf"
          : my_iszero(rhs) ? throw "//0"
          : std::forward<T>( lhs ) / std::forward<U>( rhs );
    }
    

    (hack dient nur dazu, die Überladungen für die ODR auseinanderzuhalten, weil Defaultargumente nicht Teil der Signatur sind.)
    Ob das dann Sinn macht ist eine anderer Frage und hängt davon ab, was man als "safe" betrachtet. Schließlich kann es auch hier passieren, dass plötzlich Unendlichkeiten (und dann in Folgeberechnungen NaNs) entstehen, weil das Ergebnis zu groß für eine endliche Darstellung ist.


Anmelden zum Antworten