cannot convert from 'const int' to 'const XXX *' ?



  • hi,
    folgender Quellcode:

    struct Piece
    {
    };
    
    int main()
    {
      typedef std::vector<Piece const*> Pieces;
    
      Pieces pieces(5);
    
      std::fill(pieces.begin(), pieces.end(), 0);
      return 0;
    }
    

    Beim Kompilieren mit VS 2008 kommt folgende Fehlermeldung (in xutility Zeile 3133):

    error C2440: '=' : cannot convert from 'const int' to 'const Piece *'

    Was zur Hoelle? Steh ich gerade auf dem Schlauch oder ist das totaler Quatsch?
    Mache ich:

    Piece* p = 0;
    std::fill(pieces.begin(), pieces.end(), p);
    

    Kompiliert das Dinge anstandslos 😮

    Was ist da los?



  • Als erstes: Es ist nicht nötig, die Zeiger nullzusetzen, da diese default-initialisiert werden (und ein default-initialisierter Zeiger ist immer der Nullzeiger).

    In dem Zusammenhang wird die Null als int interpretiert. Dem STL-Algorithmus std::fill() ist der Containertyp ja nicht bekannt, entsprechend versucht er, den Elementtyp über den dritten Parameter herzuleiten. Und 0 ist erstmal ein int .

    Wie gesagt, hier unnötig, aber für ein anderes Mal kannst du den Typ explizit angeben:

    std::fill(pieces.begin(), pieces.end(), static_cast<Piece const*>(0));
    


  • Genau da ist der Hund begraben. 😉 Null ist, wie der Compiler sagt, für ihn unsigned int. Dein Pointer da bestimmt nicht. Ein Nullpointer muss dementsprechend gecastet werden, ist bei tr1::shared_ptr das selbe:

    std::fill(pieces.begin(), pieces.end(), static_cast<Piece const*>(0));
    

    edit: zu spät 😃


  • Mod

    Du hast es doch schon da stehen:

    Pieces speichert Sachen vom Typ Piece const*. Der Wert 0 ist aber vom Typ int und die beiden sind nicht konvertibel.

    edit: Oha, viel zu langsam, ich mache einfach zu viel nebenher.



  • Wow, das nenn ich mal ugly...
    Bild mir ein das schon x mal gemacht zu haben.

    Naja, danke fuer die schnelle Antwort.



  • Man kann natürlich dieses - meiner Meinung nach sehr elegante - Konstrukt verwenden. nullptr kann quasi als Nullzeiger-Literal angesehen werden, es löst einige Probleme bezüglich Typsicherheit und macht explizit klar, dass es sich um einen Zeiger und keinen integralen Datentyp handelt.

    Ich habe den folgenden Header, den ich fast immer einbinde, um ihn statt 0 oder NULL zu nutzen ( NULL hat schliesslich die gleichen Probleme wie 0). Im neuen Standard wird das Schlüsselwort nullptr in C++ eingeführt, dann sollte alter Code weiterhin kompatibel und trotzdem typsicherer sein.

    #ifndef NULLPTR_HPP
    #define NULLPTR_HPP
    
    const class
    {
    	public:
    		// konvertierbar in jeden Zeigertypen
    		template <typename T>
    		operator T*() const
    		{
    			return 0;
    		}
    
    		// konvertierbar in Memberfunktionszeiger
    		template <class C, typename T>
    		operator T C::*() const
    		{
    			return 0;
    		}
    
    	private:
    		// nicht adressierbar
    		void* operator& ();
    
    } nullptr = {};
    
    #endif // NULLPTR_HPP
    

    Die Anwendung sähe dann so aus:

    std::fill(pieces.begin(), pieces.end(), nullptr);
    

    (Wie ich C++ und seine Möglichkeiten liebe :))


  • Mod

    Ich vermisse Überladungen für const-, volatile- und const-volatile-Memberfunktionen. Zudem ist der Kommentar irreführend, denn die 2. Überladung ist nicht nur für Zeiger auf nicht-const-nicht-volatile-Memberfunktionen zuständig, sondern auch für normale Zeiger auf Member.
    Zudem dürfte es zweckmäßig sein, der Klasse einen Namen zu geben, andernfalls gibt es unnötige Codeduplikation, falls das gleiche Funktionstemplate in verschiedenen ÜEs mit diesem nullptr verwendet wird. Die Spezialisierung eines Templates für ein Klassenargument ist notwendigerweise in jeder ÜE verschieden, wenn diese Klasse unbenannt ist, so wie es auch der Fall wäre, wenn sich die Klasse in einem unbenannten Namensraum befände.



  • const class
    {
       ...
    
    } nullptr = {};
    

    was macht bedeutet const class in diesem Zusammenhang, und warum muss der nullptr mit einer leeren Initialisierungsliste angegeben werden?

    Edit: Bin von selbst drauf gekommen..

    const struct {...} anonym; // const type
    


  • ahnungsloser1337 schrieb:

    Wow, das nenn ich mal ugly...
    Bild mir ein das schon x mal gemacht zu haben.

    Du kannst einen Zeiger ja auch mit dem konstanten Ausdruck 0 initialisieren oder ihm 0 (als konstanten Ausdruck) zuweisen. In Deinem Fall ist aber noch ein Funktions-Template dazwischen (std::fill), welches die Null als Referenz-auf-const-int entgegen nimmt. Das ist dann kein konstanter Ausdruck mehr.



  • Nexus schrieb:

    Als erstes: Es ist nicht nötig, die Zeiger nullzusetzen, da diese default-initialisiert werden (und ein default-initialisierter Zeiger ist immer der Nullzeiger).

    Seit wann?

    int main()
    {
    	int* f;
    	int* g=0;
    
    	std::cout<<f<<" "<<g;
    
    }
    

    7019B6F0 00000000



  • Hey, vielen Dank für die Tipps, camper! Von dir lernt man wirklich ständig wieder neue Dinge! 👍

    Stimmt, mit Memberfunktionszeigern arbeite ich nicht allzu häufig, deshalb bin ich mit der momentanen Version bisher gut ausgekommen. Ich habe den Entwurf der nullptr -Klasse von hier. Den Kommentar habe ich wohl zu wenig genau übersetzt. Eine benannte Klasse ist ja nicht sehr schwer:

    const class nullptr_t
    {
        // ...
    } nullptr;
    

    Aber wie sähe das mit den anderen Memberfunktionszeiger-Konvertierungen aus? Müsste man nicht für jede Parameterzahl eine eigene Überladung schreiben?

    ?????? schrieb:

    Seit wann?

    Seit immer, es ging schliesslich um Zeiger innerhalb std::vector .



  • Nexus schrieb:

    ?????? schrieb:

    Seit wann?

    Seit immer, es ging schliesslich um Zeiger innerhalb std::vector .

    Sag das doch gleich


  • Mod

    Nexus schrieb:

    Aber wie sähe das mit den anderen Memberfunktionszeiger-Konvertierungen aus? Müsste man nicht für jede Parameterzahl eine eigene Überladung schreiben?

    Leider ja. Anderseits sollte das hier für diese Zwecke genauso gut funktionieren:

    template<typename T>
    operator T() const
    {
        BOOST_STATIC_ASSERT(( std::tr1::is_pointer<T>::value || std::tr1::is_member_pointer<T>::value ));
        return 0;
    }
    


  • Geniale Idee, vielen Dank. 💡

    Meine Klasse sieht nun wie folgt aus. Für den Fall, dass man Boost nicht benutzt, kann man ein Makro NOT_USING_BOOST definieren und trotzdem einen relativ grossen Teil der Funktionalität nutzen:

    #ifndef NOT_USING_BOOST
     #include <boost/static_assert.hpp>
     #include <boost/type_traits/is_pointer.hpp>
     #include <boost/type_traits/is_member_pointer.hpp>
    #endif
    
    const class nullptr_t
    {
    	public:
    
    #ifdef NOT_USING_BOOST // Kein Boost: Eingeschränkte Funktionalität
    		template <typename T>
    		operator T* () const
    		{
    			return 0;
    		}
    
            template <class C, typename T> 
            operator T C::* () const 
            { 
                return 0; 
            } 
    
    #else // Boost: Alle Zeigerkonvertierungen abdecken
            template <typename P> 
            operator P() const
            {
    			BOOST_STATIC_ASSERT(boost::is_pointer<P>::value || boost::is_member_pointer<P>::value);
    			return 0; 
            } 
    #endif
    
    	private:
    		void* operator& ();
    
    } nullptr = {};
    

    Eventuell könnte ich mir noch eine Abfrage für den TR1 basteln. Gibt es da ein plattformunabhängiges Makro (ich kenne _HAS_TR1 , aber das scheint nur für MSVC++ zu gelten)? Und die doppelten Klammern bei der statischen Assertion sind nicht zwingend nötig, oder? Ich glaube, für MPL braucht man die, aber bin mir gerade nicht sicher.

    Hmm, mir ist gerade eingefallen, dass es wahrscheinlich klüger wäre, die beiden Versionen nicht zu mischen. Nicht dass man manchmal das Makro definiert und manchmal nicht und somit mehrere Klassen mit gleichem Namen erhält.

    ??????? schrieb:

    Sag das doch gleich

    Sorry, der Satz war in der Tat etwas missverständlich formuliert.



  • Mit

    ...
     template <typename P>  
     operator P() const
     ...
    

    entfernt sich die Klasse aber auch von dem eigentlichen Ziel. Beispielsweise wird dann das hier nicht mehr funktionieren:

    void foo(int);
     void foo(int*);
     void bar() {
       foo(nullptr);  // Mehrdeutig  
     }
    

    SFINAE kann man unter C++98 oder C++03 leider für Konvertierungsoperatoren nicht anwenden, oder irre ich mich? Mit C++0x wüsste ich, wie man das lösen könnte, aber dann kann man ja auch gleich nullptr benutzen. Trotzdem mal ein Beispiel, weil ich das REQUIRES-Makro so toll finde und auch anderweitig einsetzbar ist:

    #define REQUIRES(...) ,typename=typename \
      std::enable_if<(__VA_ARGS__)>::type
    
      ...
      template <typename P
        REQUIRES(    std::is_pointer<P>::value
                  || std::is_member_pointer<P>::value ) 
      >
      operator P() const
      ...
    

    Gruß,
    SP



  • Du hast Recht, daran habe ich gar nicht mehr gedacht. Ich war wohl etwas zu euphorisch, sodass ich zu wenig getestet habe. 😉

    Schwierige Entscheidung... Ich glaube, in Anbetracht der Tatsache, dass ich selten Memberzeiger habe, nehme ich mein altes Modell. Das funktioniert dann auch schön bei Leuten ohne Boost. Für die anderen Fälle ist wohl ein explizites 0 oder NULL vertretbar.

    Danke für die Beteiligung an dieser interessanten Diskussion! 🙂


Anmelden zum Antworten