Vorrang von Konvertierungsoperator & std::addressof



  • Hallo zusammen,

    aufgrund eines anderes Threads bin ich auf std::addressof aufmerksam geworden: http://en.cppreference.com/w/cpp/memory/addressof

    Von dieser Seite stammt folgender Code-Ausschnitt:

    #include <iostream>
    #include <memory>
    
    template<class T>
    struct Ptr {
       T* data;
       Ptr(T* arg) : data(arg) {}
       ~Ptr() {delete data;}
       T** operator&() { return &data; }
    };
    
    template<class T>
    void f(Ptr<T>* p) {
        std::cout << "Ptr overload called with p = " << p << '\n';
    }
    
    void f(int** p) {
        std::cout << "int** overload called with p = " << p << '\n';
    }
    
    int main()
    {
        Ptr<int> p(new int(42));
        f(&p);                 // calls int** overload
        f(std::addressof(p));  // calls Ptr<int>* overload
    }
    

    int** overload called with p = 0xbfba870c
    Ptr overload called with p = 0xbfba870c

    Okay... Jetzt verstehe ich den Sinn von std::addressof, aber ich frage mich welchen Sinn es macht, dass der Konvertierungsoperator ohne std::addressof-Verwendung eine höhere Priorität hat. Irgendetwas wird man sich ja sicherlich dabei gedacht haben...
    Hat irgendjemand evtl. eine Erklärung dafür? 😕

    Gruß,
    XSpille



  • Da gibt's keinen Konvertierungsoperator sondern da wurde operator & überladen?



  • cooky451 schrieb:

    Da gibt's keinen Konvertierungsoperator sondern da wurde operator & überladen?

    Okay... Manchmal sollte man besser ins Bett gehen... 🙄



  • interessant ist, wie dieser adressof-Operator funktioniert; dann kommt man auch drauf, was da noch alles für alles Fallstricke dabei sind.
    Man probiere folgendes mit Microsoft VC10:

    #include <iostream>
    #include <memory>
    #include <boost/utility/addressof.hpp>
    
    template<class T>
    struct Ptr {
       T* data;
       Ptr(T* arg) : data(arg) {}
       ~Ptr() {delete data;}
       T** operator&() { return &data; }
    
       operator char&() { return c_; }
       char c_;
    };
    
    template<class T>
    void f(Ptr<T>* p) {
        std::cout << "Ptr overload called with p = " << p << '\n';
    }
    void f(int** p) {
        std::cout << "int** overload called with p = " << p << '\n';
    }
    
    int main()
    {
        Ptr<int> p(new int(42));
        f(&p);                 // calls int** overload
        f(std::addressof(p));  // calls Ptr<int>* overload
        std::cout << "boost: ";
        f(boost::addressof(p));  // calls Ptr<int>* overload
    }
    

    und raus kommt:

    int** overload called with p = 0012FF50
    Ptr overload called with p = 0012FF54
    boost: Ptr overload called with p = 0012FF50
    

    wenn einem das in einem größeren Programm passiert, dann kann das Fehler suchen schon dauern.
    .. und es ist immer wieder erstaunlich, dass ein Programm, was von Leuten geschrieben wurden, die einfach engagiert sind, aber kein Geld dafür kriegen, dem Output einer weltweit operierenden Firma überlegen ist. Zumal die Mitarbeiter/Zulieferer der letzteren es einfach hätten kopieren können, denn boost::adressof war ja schon da.

    Gruß
    Werner



  • Offen gesagt verstehe ich nicht mal, wo da jetzt der Fehler ist?



  • Ergänzung zu Werners Beitrag: Beim VC11 passt es.



  • cooky451 schrieb:

    Offen gesagt verstehe ich nicht mal, wo da jetzt der Fehler ist?

    Schau auf den Output. std::adressof und boost::adressof liefern verschiedene Werte (0x0012FF54 != 0x0012FF50), d.h. mindestens eine adressof-Funktion liefert einen Pointer, der nicht auf das Objekt zeigt; liegt also falsch.
    std::adressof (MS VC10) liefert eben nicht den Pointer auf das Objekt sondern auf den char-member 'c_'.

    Passiert Dir das in einem Programm, wo dieser Pointer-Wert irgendwie weiter benutzt wird, so passiert alles mögliche, aber nicht was das Programm tun soll.

    Gruß
    Werner



  • Also wird die MS Implementierung wohl stupide die Referenz auf char& casten und davon die Addresse nehmen?
    Was ist denn die sichere Variante?

    Btw müsste

    template<typename T>
    T* addressof(T& v)
    {
        T* p = &v;
        return p;
    }
    

    nicht langen? Schließlich sind beide Referenzierungsoperatoren kein Match, da weder int** noch char& nach T* konvertiert werden können ... oder stoppt der Compiler dann anstatt als dritte Möglichkeit einfach den builtin-Operator zu verwenden, der einfach nur die Addresse des Objekts nimmt?

    Edit: Ahh ... du hast ja nur einen Referenzierungsoperator definiert, der andere ist ja kein Castoperator ... nicht richtig gelesen.


  • Mod

    Ethon schrieb:

    Was ist denn die sichere Variante?

    z.B. so

    template <typename T>
    T* addressof(T& x)
    {
        return const_cast<T*>(reinterpret_cast<const volatile T*>(&reinterpret_cast<const volatile char&>(x)));
    }
    

    Ein kleiner Nachteil gegenüber dem eingebauten &-Operator besteht darin, dass addressof nicht constexpr sein kann.



  • camper schrieb:

    Ethon schrieb:

    Was ist denn die sichere Variante?

    z.B. so

    template <typename T>
    T* addressof(T& x)
    {
        return const_cast<T*>(reinterpret_cast<const volatile T*>(&reinterpret_cast<const volatile char&>(x)));
    }
    

    Ein kleiner Nachteil gegenüber dem eingebauten &-Operator besteht darin, dass addressof nicht constexpr sein kann.

    Und warum stört hierbei nicht der Konvertierungsoperator?

    operator char&() { return c_; }
    

    char& ist ja in char const volatile& konvertierbar.


  • Mod

    reinterpret_cast führt niemals zum Aufruf von Konstruktoren oder Konvertierungsoperatoren.



  • camper schrieb:

    reinterpret_cast führt niemals zum Aufruf von Konstruktoren oder Konvertierungsoperatoren.

    Stimmt. Dann frage ich mich wie man es überhaupt falsch implementieren kann. 😕



  • Ethon schrieb:

    Stimmt. Dann frage ich mich wie man es überhaupt falsch implementieren kann. 😕

    VC10 schrieb:

    template<class _Ty> inline
    	_Ty * addressof(_Ty& _Val)
    	{	// return address of _Val
    	return ((_Ty *) &(char&)_Val);
    	}
    

    So kann man es falsch machen, Ethon 😃 .

    VC11 schrieb:

    template<class _Ty> inline
    	_Ty *addressof(_Ty& _Val) _NOEXCEPT
    	{	// return address of _Val
    	return (reinterpret_cast<_Ty *>(
    		(&const_cast<char&>(
    		reinterpret_cast<const volatile char&>(_Val)))));
    	}
    

    👍


  • Mod

    Und das ist der Grund, warum die Cast-Notation (und mit gewissen Einschränkungen auch die funktionale Notation) böse ist. Es ist nicht immer sofort offensichtlich, wie der Cast zu interpretieren ist (z.T. sogar dann, wenn der gesamte Quellcode bekannt ist 5.4./5)



  • zum Glück gibt es ja 5.2.10 11

    An lvalue expression of type T1 can be cast to the type “reference to T2” if an expression of type “pointer to T1” can be explicitly converted to the type “pointer to T2” using a reinterpret_cast. That is, a reference cast reinterpret_cast<T&>(x) has the same effect as the conversion *reinterpret_cast<T*>(&x) with the built-in & and * operators (and similarly for reinterpret_cast<T&&>(x)). The result refers to the same object as the source lvalue, but with a different type. The result is an lvalue for an lvalue reference type or an rvalue reference to function type and an xvalue for an rvalue reference to object type. No temporary is created, no copy is made, and constructors (12.1) or conversion functions (12.3) are not called.


Anmelden zum Antworten