Spezilisierung von onstruktor Templates?



  • Hallo alle zusammen. Ich hab grad ein etwas spezielles Problemchen, bezueglich eines Konstruktor templates:

    class Digsda
    {
    public:
      template <typename T>
      Dingsda(const T& rhs);
    // usw.
    };
    

    Die Klasse an sich ist also kein Template, nur der Konsturktor soll eins sein.
    Diesen will ich nun fuer bestimmte Klassen spezialisieren, z.B. als Copy-CTOR. Wie sieht da die genaue Syntax aus?

    template<>
     Dingsda::Dingsda<Dingsda>(const Dingsda& rhs) 
      {
       // specific copy ctor code here
      }
    
    template<>
     Dingsda::Dingsda<float>(const float& rhs)
      {
       // float code here
      }
    

    ist das so korrekt oder produzier ich da voelligen bloedsinn mit? meinen Buecher zufolge sollte template Spezialisierung etwa so aussehen...

    Ich moechte mir auf die Art die Verwendung sicherstellen, da meines Wissens templates keine implizite Typkonvertierung erlauben. Denn mit einem Code a la

    class Dingsda
    {
    public:
      Dingsda(const float& rhs);
      Dingsda(const Dingsda& rhs);
    //etc.
    };
    

    ist folgendes moeglich:

    int main()
    {
      int i = 3;
      Dingsda dings(i);  // i wird implizit nach float konvertiert
    }
    

    mit templates kann ich sowas unterbinden, wenn keine Spezialisierung fuer int vorhanden ist. Oder hab ich was uebersehn?



  • naja der sinn von templates ist eingentlich der das man sowenig wie möglich spezialisiert.

    für alles andere kann man normale überladene funktionen (in dem fall konstruktoren) schreiben (wo auch implizite typenkonvertierung funktioniert, oder nicht wenn man explizit angibt).

    class Dingsda
    {
    public:
      template <typename T>
      Dingsda(T const& rhs);
    
      Dingsda(float rhs);
      Dingsda(Dingsda const& rhs);
    };
    
    template <typename T>
    Dingsda::Dingsda(T const& rhs)
    {
     //generische implementation
    }
    
    template <>
    Dingsda::Dingsda<char> (char const& rhs)
    {
     //template spezialisierung (erlaubt keine implizite konstrution)
    }
    
    Dingsda::Dingsda(float rhs)
    {
     //spezifische implementation (durch überladung, erlaubt implizites konstruieren)
    }
    
    Dingsda::Dingsda(Dingsda const& rhs)
    {
     //genauso wie oben
    }
    
    int main(int, char**)
    {
     Dingsda f(1); // Dingsda::Dingsda(float)
     Dingsda d(f); // Dingsda::Dingsda(Dingsda const&)
     Dingsda c('c'); //Dingsda::Dingsda<char>(char const&)
    
     IrgendEineKlasse c;
    
     Dingsda ic(c); //Dingsda::Dingsda<T>(T const&) 
    }
    


  • Besser ist es du nimmst überhaupt keine templates und überlädst die Konstruktoren direkt. Um dieimplizite Typkonvertierung zu unterbinden, gibt es das Schlüsselwort 'explicit':

    class Dingsda
    {
    public:
      explicit Dingsda(float rhs);
      explicit Dingsda(Dingsda const& rhs);
    };
    


  • Th schrieb:

    Besser ist es du nimmst überhaupt keine templates und überlädst die Konstruktoren direkt. Um dieimplizite Typkonvertierung zu unterbinden, gibt es das Schlüsselwort 'explicit':

    class Dingsda
    {
    public:
      explicit Dingsda(float rhs);
      explicit Dingsda(Dingsda const& rhs);
    };
    

    Das würde leider bei einem Aufruf wie:

    Dingsda dings(6);
    

    nicht helfen, da der Compiler sowas draus macht:

    Dingsda dings( /*Dingsda(float)->*/Dingsda(6)/*Dingsda(copy)*/ );
    


  • pumuckl schrieb:

    Konstruktor templates

    Sowas nennt man auch gerne "Typumwandlungskonstruktor" 😉



  • inp schrieb:

    ...
    

    Seltsam 😕 Wieso schluckt das mein gcc nicht ?
    Ihm gefällt das hier nicht:

    Dingsda::Dingsda<char>
    

    Fragt sich anscheinend wieso Dingsda kein Klassentemplate ist, obwohl dann das <..> ja hinterm ersten Dingsda sein müsste ...

    Aber Comeau meckert nicht, dann ist ja alles gut 😉



  • KasF schrieb:

    Th schrieb:

    Besser ist es du nimmst überhaupt keine templates und überlädst die Konstruktoren direkt. Um dieimplizite Typkonvertierung zu unterbinden, gibt es das Schlüsselwort 'explicit':

    class Dingsda
    {
    public:
      explicit Dingsda(float rhs);
      explicit Dingsda(Dingsda const& rhs);
    };
    

    Das würde leider bei einem Aufruf wie:

    Dingsda dings(6);
    

    nicht helfen, da der Compiler sowas draus macht:

    Dingsda dings( /*Dingsda(float)->*/Dingsda(6)/*Dingsda(copy)*/ );
    

    das seh ich anders

    class Dingsda
    {
    public:
      explicit Dingsda(float rhs);
      explicit Dingsda(Dingsda const& rhs);
    };
    ...
    Dingsda dings(6);
    

    da der ctor float definiert ist wird nur dieser ctor aufgerufen
    davor wird int zu float gecastet.

    explizit wird eigentlich verwendet um soetwas zu verhindern

    class Dingsda3;
    class Dingsda2;
    
    class Dingsda
    {
    	float m_f;
    public:
    	explicit Dingsda(float) : m_f(0) 
    	{ }
    
    	/* explicit */ Dingsda(Dingsda const& other) : m_f(other.m_f)
    	{ }
    
    	/* explicit */ Dingsda(Dingsda3 const&) {};
    };
    
    class Dingsda2
    {
    	float m_f;
    public:
    	Dingsda2() : m_f(1.0f)
    	{ };
    
    	operator Dingsda()
    	{	return Dingsda(m_f);	//expliziter aufruf Dingsda::Dingsda(float)
    	}
    };
    
    class Dingsda3
    {
    public:
    	Dingsda3(int) {};
    };
    
    int main(int, char**)
    {
    	Dingsda d(Dingsda2);	// error : wenn Dingsda::Dingsda(Dingsda const&) als explizit definiert
    							// ansonsten : explizit Dingsda2::Dingsda2(), implizit static_cast<Dingsda>(exlizit kreirtes dingsda2), impliziter Dingsda(Dingsda const&) aufruf
    
    	Dingsda d2 = 1;			// error : wenn Dingsda::Dingsda(Dingsda3 const&) als explizit definiert
    							// ansonsten : geht nicht über Dingsda::Dingsda(float) da als explizit definiert
    							// sondern implizit Dingsda3::Dingsda3(int), implizit Dingsda::Dingsda(Dingsda3 const&)
    
    	return 0;
    }
    


  • KasF schrieb:

    inp schrieb:

    ...
    

    Seltsam 😕 Wieso schluckt das mein gcc nicht ?
    Ihm gefällt das hier nicht:

    Dingsda::Dingsda<char>
    

    Fragt sich anscheinend wieso Dingsda kein Klassentemplate ist, obwohl dann das <..> ja hinterm ersten Dingsda sein müsste ...

    Aber Comeau meckert nicht, dann ist ja alles gut 😉

    hmmh, hast recht (vs2005 auch)
    die syntax kommt mir richtig vor
    ich hab hab <char> weggetan damit der compiler den typen automatisch rausfindet.
    so lässts sich kompilieren.

    template<>
    Dingsda::Dingsda(char const&)
    { }
    


  • inp schrieb:

    KasF schrieb:

    Th schrieb:

    Besser ist es du nimmst überhaupt keine templates und überlädst die Konstruktoren direkt. Um dieimplizite Typkonvertierung zu unterbinden, gibt es das Schlüsselwort 'explicit':

    class Dingsda
    {
    public:
      explicit Dingsda(float rhs);
      explicit Dingsda(Dingsda const& rhs);
    };
    

    Das würde leider bei einem Aufruf wie:

    Dingsda dings(6);
    

    nicht helfen, da der Compiler sowas draus macht:

    Dingsda dings( /*Dingsda(float)->*/Dingsda(6)/*Dingsda(copy)*/ );
    

    das seh ich anders

    class Dingsda
    {
    public:
      explicit Dingsda(float rhs);
      explicit Dingsda(Dingsda const& rhs);
    };
    ...
    Dingsda dings(6);
    

    da der ctor float definiert ist wird nur dieser ctor aufgerufen
    davor wird int zu float gecastet.

    Hrmpf, hast natürlich vollkommen Recht. In letzter Zeit schreibe ich immer viel Unsinn. Sollte besser mal meine Post's genauer lesen 🙂
    Mir ist auch natürlich klar was es mit explicit auf sich hat.

    Trotzdem hilft hier die Lösung von Th nicht, um die Konvertierung von int to float zu verhindern ...



  • das stimmt

    alternativ kann man den "int" konstruktor private definieren, somit gibts ihn für einen typen (und keine impliziten spielchen werden probiert) da er im private bereich ist kann man ihn nicht ausführen.

    so:

    class Dingsda
    {
    public:
     Dingsda(float) {};
    
    private:
     Dingsda(int) {}; //speziell für einen typen konstruktion abdrehen
    
     Dingsda(Dingsda const&) {}; // template<typename T> beinhaltet copy construktor nicht, wenn nicht vorhanden wird er generiert !!!!
    
     template<typename T>
     Dingsda(T const&) {}; //für alle typen konstrkution abdrehen, ausnahme die public definierten nicht "template basierten" konstrkutoren 
    };
    
    int main(int, char**)
    { 
     Dingsda d(1), //error , private Dingsda(int)
    		 d2 = 2; //error , private Dingsda(int)
    
     Dingsda d(1.0), //error , private template<typename T> Dingsda(T)
    	     d2 = 2.0; //error , private template<typename T> Dingsda(T)
    
     Dingsda f(1.0f), //ok
    	     f2 = 2.0f; //ok
    
     Dingsda c = f; //error , private Dingsda(Dingsda const&)
    		 c2 = f2; //error , private Dingsda(Dingsda const&)
    
     return 0;
    }
    

    die regeln hierfür, soweit ich mich erinnern kann

    1. schauen ob konstruktor auf direktem wege gefunden wird
    2. schauen ob konstruktor durch templates gefunden wird (ausgenommen copykonstruktor mit eigenem typen)
    3. schauen ob konstruktor auf indirektem wege gefunden wird (durch implizites herumgecaste (aber nicht 10000mal hintereinander, sondern maximal 1mal))


  • Die Konstruktion mit bestimmten Argumenten zu verhindern ist ja kein schweres Ding.
    Ich hatte es eher so verstanden, dass pumuckl für bestimmte Typen, bestimmte Konstruktoren aufrufen will, aber dennoch grundlegende Konvertierung wie int to float verhindern wollte ...

    Dazu fällt mir aber auch keine andere Methode ein, wie die mit Templates von dir.


  • Mod

    inp schrieb:

    Dingsda f(1.0f), //ok
    	     f2 = 2.0f; //ok
    

    nicht ganz. Für f2 brauchen wir einen Copy-Konstruktor - auch dann, wenn dieser ausgelassen wird. Im Übrigen ist es zweckmäßig, diese privaten Konstruktoren nicht zu definieren, um auch dann, wenn wir es mit friends oder Funktionen der Klasse zu tun haben, wenigstens einen Linkerfehler zu erhalten.

    Ein andere Methode bedient sich SFINAE um unerwünschte Spezialisierungen zu unterdrücken bzw. erwünschte zuzulassen (geht auch ohne boost - aber so ist es einfacher)

    #include "boost/utility.hpp"
    #include "boost/mpl/vector.hpp"
    #include <boost/mpl/contains.hpp>
    
    namespace mpl = boost::mpl;
    
    class Dingsda // : boost::noncopyable
    {
    public:
        template<typename T>
        Dingsda(const T& t,
            typename boost::disable_if< typename mpl::contains< mpl::vector<
                int, char, void*, float, double                  // Für alle Typen außer den aufgezählten
            >, T >::type >::type* = 0) {}
    
        template<typename T>
        Dingsda(const T& t,
            typename boost::enable_if< typename mpl::contains< mpl::vector<
                float                                            // Für alle aufgezählten Typen
            >, T >::type >::type* = 0) {}
    
    private:
        Dingsda(Dingsda&);
        void operator=(Dingsda&);
    };
    
    int main(int, char**)
    {
     Dingsda d(1), //error, kann nicht in Dingsda& konvertieren
             d2 = 2; //error, kann nicht in Dingsda& konvertieren
    
     Dingsda e(1.0), //error, kann nicht in Dingsda& konvertieren
             e2 = 2.0; //error, kann nicht in Dingsda& konvertieren
    
     Dingsda f(1.0f), //ok
             f2 = 2.0f; //fehler, kann rvalue nicht an referenz binden
    
     Dingsda c = f; //error , private Dingsda(Dingsta&)
    
     return 0;
    }
    


  • camper schrieb:

    inp schrieb:

    Dingsda f(1.0f), //ok
    	     f2 = 2.0f; //ok
    

    nicht ganz. Für f2 brauchen wir einen Copy-Konstruktor - auch dann, wenn dieser ausgelassen wird. Im Übrigen ist es zweckmäßig, diese privaten Konstruktoren nicht zu definieren, um auch dann, wenn wir es mit friends oder Funktionen der Klasse zu tun haben, wenigstens einen Linkerfehler zu erhalten.

    Ein andere Methode bedient sich SFINAE um unerwünschte Spezialisierungen zu unterdrücken bzw. erwünschte zuzulassen (geht auch ohne boost - aber so ist es einfacher)

    #include "boost/utility.hpp"
    #include "boost/mpl/vector.hpp"
    #include <boost/mpl/contains.hpp>
    
    namespace mpl = boost::mpl;
    
    class Dingsda // : boost::noncopyable
    {
    public:
        template<typename T>
        Dingsda(const T& t,
            typename boost::disable_if< typename mpl::contains< mpl::vector<
                int, char, void*, float, double                  // Für alle Typen außer den aufgezählten
            >, T >::type >::type* = 0) {}
    
        template<typename T>
        Dingsda(const T& t,
            typename boost::enable_if< typename mpl::contains< mpl::vector<
                float                                            // Für alle aufgezählten Typen
            >, T >::type >::type* = 0) {}
    
    private:
        Dingsda(Dingsda&);
        void operator=(Dingsda&);
    };
    
    int main(int, char**)
    {
     Dingsda d(1), //error, kann nicht in Dingsda& konvertieren
             d2 = 2; //error, kann nicht in Dingsda& konvertieren
    
     Dingsda e(1.0), //error, kann nicht in Dingsda& konvertieren
             e2 = 2.0; //error, kann nicht in Dingsda& konvertieren
    
     Dingsda f(1.0f), //ok
             f2 = 2.0f; //fehler, kann rvalue nicht an referenz binden
    
     Dingsda c = f; //error , private Dingsda(Dingsta&)
    
     return 0;
    }
    

    yup 🙂
    wollt die schweren geschütze nicht ausfahren 😃

    mit template meta kann man sich aber auch unnötig viel spielen 😃

    edit:
    das mit undefiniertem body leuchtet ein, ty für tip.



  • Naja, das mit boost erscheint mir bissl overkill. Wenn ich (neben dem Copy-Ctor) das Konstruktor template wie oben von mir geschrieben deklariere, aber abgesehen von den drei oder vier Spezialisierungen für die Typen die ich zulassen will keine allgemeine Definition liefere, wird er mir auf jeden Fall schon zur Compilezeit Linkerfehler geben, falls ich das mit nem falschen Typ versuche oder?


Anmelden zum Antworten