Pointer auf Klassen-Instanzen einer Template Vorlage



  • Hallo zusammen,

    nachdem ich etliche Beiträge in diesem und anderen Foren verschlungen und Nächte durchprobiert habe, muss ich mal um Hilfe bitten.

    Ich habe eine Template Vorlage, die einen Vector auf eine andere simple Klasse enthält.
    Ich möchte nun gerne dynamisch mehrere Instanzen auf Basis des Templates für den gleichen Dialog erzeugen und dafür einen einzigen Pointer nutzen, der dann auf die versch. Template Instanzen zeigen kann (z.B. <int>, <string>).

    Folgendes habe ich bereits probiert:

    Colorlist<T> *p;
    
    error C2065: 'T': nichtdeklarierter Bezeichner
    

    (macht Sinn bei einer Vorlage, ist ja noch keine Klasse) 😉

    void *p;
    Colorlist<int> ci;
    p = &ci;
    
    error C2228: Der linke Teil von '.addValue' muss eine Klasse/Struktur/Union sein
    

    (void bleibt void) 🤡

    "Non-Template pure virtual" Basisklasse als Typ für den Pointer

    class Base
    {
    protected:
    	Base() {}
    	~Base() {}
    	virtual void addValue(T) = 0;
    	virtual void printValues(void) = 0;
    };
    
    template <class T> 
    class Colorlist : Base
    {...}
    
    error C2061: Syntaxfehler: Bezeichner 'T'
    

    (worher soll Base auch T kennen) 🙄

    Hier mal der reduzierte Testcode, wie ich ihn mir wünschen würde...

    #include <tchar.h>
    #include <iostream>
    #include <string>
    
    using namespace std;
    
    #include <vector>
    
    template <class T>
    class Color 
    {
    public :
    	Color(T& inv ) { value = inv; }
    	T GetValue(void) { return value; }
    	T value;
    };
    
    class Base
    {
    protected:
    	Base() {}
    	~Base() {}
    	virtual void addValue(T) = 0;
    	virtual void printValues(void) = 0;
    };
    
    template <class T> 
    class Colorlist : Base
    {
    public :
    	Colorlist() {}
    	~Colorlist() { vec.clear(); }
    
    	void addValue(T n)
    	{
    		vec.push_back( Color<T>(n) );
    	}
    
    	void printValues(void)
    	{
    		for( it=vec.begin(); it!=vec.end(); it++ )
    			cout << it->GetValue() << endl;
    	}
    
    private:
    	vector< Color<T> > vec;
    	typename vector< Color<T> >::iterator it;
    };
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	Colorlist<int> ci;
    	Colorlist<string> cs;
    	Base *p;
    
    	p = &ci;
    	p->addValue(10);
    	p->addValue(20);
    	p->printValues();
    
    	p = &cs;
    	p->addValue("aaa");
    	p->addValue("bbb");
    	p->printValues();
    	return 0;
    }
    

    Gibt es hierzu eine ädaquate Lösung, z.B. eine Vorwärtedeklaration von <T> für die Base-Klasse?

    Vielen Dank im Voraus!

    Gruß,
    Frank.

    Edit: Beschreibung der Form halber korrigiert



  • #include <iostream>
    #include <string>
    #include <vector>
    
    using namespace std; 
    
    template <class T> 
    class Color 
    { 
    public : 
        Color(T& inv ) { value = inv; } 
        T GetValue() { return value; } 
        T value; 
    }; 
    
    template<typename T>
    class Base 
    { 
    public: 
        Base() {} 
        ~Base() {} 
        virtual void addValue(T) = 0; 
        virtual void printValues() = 0; 
    }; 
    
    template <class T> 
    class Colorlist : Base<T>
    { 
    public : 
        Colorlist() {} 
        ~Colorlist() { vec.clear(); } 
    
        void addValue(T n) 
        { 
            vec.push_back( Color<T>(n) ); 
        } 
    
        void printValues() 
        { 
            for( it=vec.begin(); it!=vec.end(); it++ ) 
                cout << it->GetValue() << endl; 
        } 
    
    private: 
        vector< Color<T> > vec; 
        typename vector< Color<T> >::iterator it; 
    }; 
    
    int main(int argc, char* argv[])
    {
    	Colorlist<int> ci;
        Colorlist<string> cs;
        void *p;
    
        p = &ci;
        static_cast<Colorlist<int>*>(p)->addValue(10);
        static_cast<Colorlist<int>*>(p)->addValue(20);
        static_cast<Colorlist<int>*>(p)->printValues();
    
        p = &cs; 
        static_cast<Colorlist<string>*>(p)->addValue("aaa");
        static_cast<Colorlist<string>*>(p)->addValue("bbb");
        static_cast<Colorlist<string>*>(p)->printValues();
    
        return 0;
    }
    


  • Hallo Airdamn,

    wow, das ging ja fix 😮
    Hatte beim Casten wohl die falsche Syntax.
    So funktioniert es perfekt, erstmal vielen Dank!

    Würde es aber gerne noch abkürzen.
    Aus C bin ich noch das #define gewohnt, was auch funktioniert.
    Aber es gibt doch in C++ bestimmt was "schöneres", oder?

    //geht
    #define PINT static_cast<Colorlist<int>*>(p)
    PINT->printValues();
    
    //geht nicht
    typedef PINT static_cast<Colorlist<int>*>(p);
    

    Gruß,
    Frank.



  • nur mal so,

    was ist eine Instanz eines Templates? Oder anders gefragt kann ein Template eine Instanz sein?

    Das was Airdamn vorschlägt ist schon ziemlich brutal und hebelt die Template und C++ Überlegungen hintenrum wieder aus.
    - Und am Ende ist alles binär und void* -



  • idefix schrieb:

    nur mal so,
    was ist eine Instanz eines Templates? Oder anders gefragt kann ein Template eine Instanz sein?

    Hast Recht, Templates kann man nicht instanziieren, sondern nur Klassen, die auf diesem Template basieren.

    idefix schrieb:

    Das was Airdamn vorschlägt ist schon ziemlich brutal und hebelt die Template und C++ Überlegungen hintenrum wieder aus.
    - Und am Ende ist alles binär und void* -

    Mag sein, aber bislang für mich die einzige Lösung.
    Verschachtelte Templates und Polymorphismus lassen sich scheinbar schwer vereinen.
    Weisst Du einen eleganteren Weg?



  • Templateinstanz wäre doch wenn ich ein Template instanziere also

    std::vector<int> intvec;
    

    Eigentlich haben Instanzen von Templates mit unterschiedlichen Typen ja
    keine gemeinsame Basis.

    Nun will der op (warum auch immer) mit einem Pointer auf verschiedene Instanzen des gleichen Templates (mit unterschiedlichen Typen) zeigen.

    @modestia

    mit fällt spontan nur das ein:

    template<typename NewType>
    NewType* to_type(void* p)
    {
    	return static_cast<NewType*>(p);
    }
    int main(int argc, char* argv[])
    {
        Colorlist<int> ci; 
        Colorlist<string> cs; 
        void *p;
    
        typedef Colorlist<int>* (*PINTFUNC)(void*);
        typedef Colorlist<string>* (*PSTRINGFUNC)(void*);
        PINTFUNC PINT = to_type<Colorlist<int> >;
    
        p = &ci; 
        PINT(p)->addValue(10); 
        PINT(p)->addValue(20); 
        PINT(p)->printValues(); 
    
        p = &cs;
        PSTRINGFUNC PSTRING = to_type<Colorlist<string> >;
        PSTRING(p)->addValue("aaa");
        PSTRING(p)->addValue("bbb");
        PSTRING(p)->printValues();
    
        return 0;
    }
    


  • Airdamn schrieb:

    Eigentlich haben Instanzen von Templates mit unterschiedlichen Typen ja
    keine gemeinsame Basis.

    Yep, deswegen ja die Idee mit der abstrakten Basisklasse.
    Wäre auch problemlos, wenn "Base" hier nicht selbst die verschiedenen Typen als Member aufnehmen müsste.
    Alternativ könnte man auch möglichst viel Funktionalität in die Basisklasse packen und dann halt für jeden Typ ein Child mit virtuals erzeugen.
    Aber wenn schon C++, dann möchte ich auch auf switch/if/else in Dialogen weitgehend verzichten.



  • nun ich möchte hier nicht herumtrollen, denn der Vorschlag ist durchaus legitim und führt zum gewünschtem Ergebnis.

    Einander wäre dann umständlicher und mühseliger. Wenn an der Vorgabe nicht gerüttelt werden kann/soll. Zum Beispiel könnte man dem Template Color eine TypenId zuweisen und von eine Basisklasse ColorBase ableitet.

    Für mich sieht es so aus, das entweder die Typenüberprüfung verloren geht oder das Casting nicht notwendig ist.

    Ansonsten nur über das Design. Deshalb auch meine Frage.

    class Base
    {
    public:
        Base() {}
        virtual ~Base() {}    
        virtual void printValues() = 0;
    }; 
    
    template <class T>
    class Colorlist:public Base
    {
    public : 
    
        Colorlist() {}
        virtual ~Colorlist() { vec.clear(); }
    
        void addValue(T n)
        {      
            vec.push_back( Color<T>(n) );
        }
    
        virtual void printValues()
        {
            for( it=vec.begin(); it!=vec.end(); it++ )
                cout << it->GetValue() << endl;
        }
    
    private:
        vector< Color<T> > vec;
        typename vector< Color<T> >::iterator it;
    };
    int main(int argc, char* argv[])
    {
        Colorlist<int> ci;
        Colorlist<string> cs;
        Base *p;
    
     ci.addValue(10);
     ci.addValue(20);
    
     cs.addValue("aaa");
     cs.addValue("bbb");
    
    p=&ci;
    p->printValues();
    
    p=&cs;
    p->printValues();
    }
    


  • @Idefix:

    Bin gerade etwas sprachlos, dass Dein Vorschlag nun funktioniert, da ich genau an dieser Methode verzweifelt bin 🙄

    error C2440:
    'Initialisierung': 'Colorlist<T> *' kann nicht in 'Colorbase *' konvertiert werden
    with
    [
    T=int
    ]

    Genauso gibt es den anderen Fall, dass beim Kompilieren erstmal nicht zwangsläufig ein Fehler gemeldet wird, so lange keine Klasse vom Template erzeugt wird.
    "Erstellen -> Projekt neu erstellen" bringt dann die Fehler erst zum Vorschein.



  • Ok, in meinem Ursprungs-Projekt kann es so doch nicht gehen, da ich über den Pointer natürlich auch die anderen 4711 Funktionen aufrufen will/muss.
    Die Voraussetzung ist dann natürlich, dass alle überschriebenen Memberfunktionen in der abstrakten Basisklasse (mitsamt <T> Identifier vorliegen müssten.

    Sorry für die Verwirrung, aber bei den ganzen Testprojekten verliert man schon mal den Überblick 🤡
    Eure Inputs haben mir dennoch weitergeholfen und mein grundsätzliches Verständnis verifiziert.

    Vielen Dank!


  • Mod

    modestia schrieb:

    Ok, in meinem Ursprungs-Projekt kann es so doch nicht gehen, da ich über den Pointer natürlich auch die anderen 4711 Funktionen aufrufen will/muss.
    Die Voraussetzung ist dann natürlich, dass alle überschriebenen Memberfunktionen in der abstrakten Basisklasse (mitsamt <T> Identifier vorliegen müssten.

    Grundsätzlich besteht natürlich die Problematik, dass keine statische Typüberprüfung stattfinden kann, der Aufruf einer Funktion mit dem falschen T könnte einfach abgefangen werden und fehlschlagen, oder man verzichtet auf eine Überprüfung und erhält dann im Allgemeinen undefiniertes Verhalten. Templates können nicht virtuell sein, aber nichts hindert uns daran, in einem Template eine virtuelle Funktion aufzurufen. Zum Beispiel:

    #include <tchar.h>
    #include <iostream>
    #include <string>
    #include <vector>
    
    using namespace std;
    
    template <class T>
    class Color
    {
    public :
        Color(const T& inv ) : value( inv ) {}
        T GetValue() const { return value; }
        T value;
    };
    
    class Base
    {
    protected:
        Base() {}
    public:
        virtual ~Base() {}
        template <class T>
        void addValue(const T& v);
        virtual void printValues() const = 0;
    private:
        virtual void do_addValue(const void*) = 0;
    };
    
    template <class T>
    class Colorlist : public Base
    {
    public :
        Colorlist() {}
        ~Colorlist() { vec.clear(); }
    
    public:
        // verdeckt ganz nebenbei das Template Base::addValue, was ja auch richtig ist
        void addValue(const T& v ) { vec.push_back( Color<T>( v ) ); }
    private:
        void do_addValue(const void* p)
        {
            // ggf. undefiniert, falls mit falschem Typ aufgerufen
            addValue( *static_cast< const T* >( p ) );
        }
    public:
        void printValues() const
        {
            for( it=vec.begin(); it!=vec.end(); it++ )
                cout << it->GetValue() << endl;
        }
    
    private:
        vector< Color<T> > vec;
        typename vector< Color<T> >::iterator it;
    };
    
    // Hilfstemplate, um vom Argumenttyp const char* auf std::string zu schließen
    template <class T>
    struct constchar2str { typedef T type; };
    template <>
    struct constchar2str<const char*> { typedef std::string type; };
    
    template <class T>
    void Base::addValue(const T& v);
    {
        typedef typename constchar2str< T >::type value_type;
        const value_type& value = v;
        // ohne Prüfung:
        do_addValue( &value );
        // mit Prüfung:
        dynamic_cast< Colorlist<value_type>& >( *this ).do_addValue( &value );
        // oder ohne virtuellen Aufruf (wenn wir sowieso casten)
        dynamic_cast< Colorlist<value_type>& >( *this ).addValue( v );
        // oder ohne Prüfung
        static_cast< Colorlist<value_type>& >( *this ).addValue( v );
    }
    
    int main()
    {
        Colorlist<int> ci;
        Colorlist<string> cs;
        Base *p;
    
        p = &ci;
        p->addValue(10);
        p->addValue(20);
        p->printValues();
    
        p = &cs;
        p->addValue("aaa");
        p->addValue("bbb");
        p->printValues();
        return 0;
    }
    


  • Hi Camper,

    mit Deiner Konstruktion bin ich jetzt erstmal beschäftigt.
    Habe mit dem Lesen und Verstehen schon echte Probleme ... Reschpeckt und Danke! 😃

    Gruß,
    Frank.



  • der Ansatz ist äußerst interesant!

    Einige Probleme habe ich leider.

    1. Der Typenumsetzer constchar2str funktioniert nur bei

    void Base::addValue(const T v)
    // und nicht bei
    void Base::addValue(const T& v)
    

    bei der Referenz ist der Typ

    const char[4]&
    

    2. Die Überladung der Funktion addValue, denn die Funktionen in der Klasse
    Colorlist wird nicht angesprochen. Auch bei int nicht.

    dynamic_cast< Colorlist<value_type>& >( *this ).do_addValue( &value );
    
    error C2248: 'Colorlist<T>::do_addValue' : cannot access private member 
    declared in class 'Colorlist<T>'
    1>        with
    1>        [
    1>            T=int
    1>        ]
    

    Habe nur ich das Problem?


  • Mod

    Sowas passiert, wenn man den Compiler nicht zuerst fragt. Ausgebessert, Variante (2) ist sowieso die am wenigsten sinnvolle. (3) oder (4) dürfte das Rennen machen. Ich bin aber nicht sicher, ob das überhaupt gutes Design darstellen kann; eigentlich wollte ich nur die Möglichkeit an sich darstellen.

    #include <cstddef>
    #include <iostream>
    #include <string>
    #include <vector>
    
    using namespace std;
    
    template <class T>
    class Color
    {
    public :
        Color(const T& inv ) : value( inv ) {}
        T GetValue() const { return value; }
        T value;
    };
    
    class Base
    {
    protected:
        Base() {}
    public:
        virtual ~Base() {}
        template <class T>
        void addValue(const T& v);
        virtual void printValues() const = 0;
    private:
        virtual void do_addValue(const void*) = 0;
    };
    
    template <class T>
    class Colorlist : public Base
    {
    public :
        Colorlist() {}
        ~Colorlist() { vec.clear(); }
    
    public:
        // verdeckt ganz nebenbei das Template Base::addValue, was ja auch richtig ist
        void addValue(const T& v ) { vec.push_back( Color<T>( v ) ); }
    private:
        void do_addValue(const void* p)
        {
            // ggf. undefiniert, falls mit falschem Typ aufgerufen
            addValue( *static_cast< const T* >( p ) );
        }
        // für (2)
        friend class Base;
    public:
        void printValues() const
        {
            for( typename vector< Color<T> >::const_iterator it=vec.begin(); it!=vec.end(); it++ )
                cout << it->GetValue() << endl;
        }
    
    private:
        vector< Color<T> > vec;
        typename vector< Color<T> >::iterator it;
    };
    
    template <class T>
    void Base::addValue(const T& v)
    {
        typedef typename constchar2str< T >::type value_type;
        const value_type& value = v;
        // (1) ohne Prüfung:
        do_addValue( &value );
        // (2) mit Prüfung:
        dynamic_cast< Colorlist<value_type>& >( *this ).do_addValue( &value );
        // (3) oder ohne virtuellen Aufruf (wenn wir sowieso casten)
        dynamic_cast< Colorlist<value_type>& >( *this ).addValue( v );
        // (4) oder ohne Prüfung
        static_cast< Colorlist<value_type>& >( *this ).addValue( v );
    }
    
    // Hilfstemplate, um vom Argumenttyp const char* auf std::string zu schließen
    template <class T>
    struct constchar2str { typedef T type; };
    template <std::size_t N>
    struct constchar2str< char[N] > { typedef std::string type; };
    template <>
    struct constchar2str<const char*> { typedef std::string type; };
    
    int main()
    {
        Colorlist<int> ci;
        Colorlist<string> cs;
        Base *p;
    
        p = &ci;
        p->addValue(10);
        p->addValue(20);
        p->printValues();
    
        p = &cs;
        p->addValue("aaa");
        p->addValue("bbb");
        p->printValues();
        return 0;
    }
    


  • Hi Camper,

    Deine aufgezeigten Möglichkeiten sind in der Tat sehr interessant.
    Nach der Änderung bekomme ich aber nun folgende Meldungen:

    68:    dynamic_cast< Colorlist<value_type>& >( *this ).do_addValue( &value ); 
    70:    dynamic_cast< Colorlist<value_type>& >( *this ).addValue( v );
    

    warning C4541: 'dynamic_cast' für polymorphen Typ 'Base' mit /GR- verwendet; unvorhersehbares Verhalten möglich

    13:    T GetValue() const { return value; }
    

    error C2090: Funktionsergebnis ist ein Array
    Siehe Verweis auf Instanziierung der kompilierten Klassenvorlage 'Color<T>'
    with
    [
    T=const value_type
    ]
    Bei der Kompilierung der Memberfunktion 'void Colorlist<T>::addValue(T (&))' der Klassenvorlage
    with
    [
    T=const value_type
    ]
    Siehe Verweis auf Instanziierung der kompilierten Klassenvorlage 'Colorlist<T>'
    with
    [
    T=const value_type
    ]
    Siehe Verweis auf Instanziierung der kompilierten Funktionsvorlage 'void Base::addValue<const char[4]>(T (&))'
    with
    [
    T=const char [4]
    ]

    Habe dann die Compiler-Option /GR hinzugefügt, was zumindest die erste Meldung unterdrückt.
    Die 2. Meldung bleibt aber bestehen.

    Gruß


  • Mod

    modestia schrieb:

    Hi Camper,

    Deine aufgezeigten Möglichkeiten sind in der Tat sehr interessant.
    Nach der Änderung bekomme ich aber nun folgende Meldungen:

    68:    dynamic_cast< Colorlist<value_type>& >( *this ).do_addValue( &value ); 
    70:    dynamic_cast< Colorlist<value_type>& >( *this ).addValue( v );
    

    warning C4541: 'dynamic_cast' für polymorphen Typ 'Base' mit /GR- verwendet; unvorhersehbares Verhalten möglich

    RTTI wird nunmal für dynamic_cast benötigt und /GR ist auch Standard.

    Die 2. Meldung bleibt aber bestehen.

    Das dürfte ein Compilerbug sein. T sollte eigentlich als char[4] deduziert werden. Das kann man aber leicht umgehen mit dieser zusätzlichen partiellen Spezialisierung:

    template <std::size_t N>
    struct constchar2str< const char[N] > { typedef std::string type; };
    


  • camper schrieb:

    RTTI wird nunmal für dynamic_cast benötigt und /GR ist auch Standard.

    Wundert mich, dass /GR im VS2003 nicht standardmässig eingeschaltet ist.
    Werde dann mal den Wizard entsprechend ändern.

    camper schrieb:

    Das dürfte ein Compilerbug sein. T sollte eigentlich als char[4] deduziert werden. Das kann man aber leicht umgehen mit dieser zusätzlichen partiellen Spezialisierung:

    template <std::size_t N>
    struct constchar2str< const char[N] > { typedef std::string type; };
    

    Danke, nun geht es 👍



  • Kurzes Update:

    Ziel des Ganzen war es, mehrere Listen mit versch. Typen in einem WinAPI-Dialog (ohne MFC) darstellen zu können (ListView etc.).
    Innerhalb von WM_INITDIALOG sollte dann der 'neutrale' Base Pointer entsprechend gesetzt werden, um um die Controls mit den jeweils notwendigen Daten zu füllen.

    Eine andere Lösung besteht darin, dem ganzen nochmal eine Dialogklasse nach Muster des Win32++ Frameworks überzustülpen.

    template <class T>
    class Colordialog : public Colorlist<T>, public Dialog
    ...
    

    Somit erzeugt man die jeweiligen Dialogobjekte aus dem Template, die dann auf den jeweils eigenen Typen-Collector zugreifen.
    Der nächste Schritt wäre ein PropertySheet, welches dynamisch Pages für jede Instanz anlegt.

    Viele Wege führen nach Rom, aber die hier gezeigten Lösungen waren schon sehr pfiffig und zeigen einem das mögliche Potential von C++.


Anmelden zum Antworten