Problem mit Templates und Vererbung



  • Hey Community,

    ich habe gerade ein kleines großes Problem mit Templates und Vererbung. Kurz umrissen um was es geht: Gegeben ist eine Containerklasse, die als Templateargument einen Typ übergeben bekommt (Item-Typ), von dem der Container einen Vektor hält. Eben ein typischer Container von Items.

    Die Item-Klasse besteht aus einer Basisklasse, von der die eigentlichen Items erben. Wenn ein neues Item erstellt wird, wird es intern über einen Pointer mit dem Container verknüpft. Hier kurz eine entschlackte Kurzfassung die das grundlegende Modell darstellt:

    template<typename TItem>
    class Container
        {
        private:
    	std::vector<TItem*> items;
    
        public:
    	TItem* newItem()
    	{
    	    TItem *i = new TItem;
    	    i->container = this;    // Fehler! Der implizite cast schlägt fehl
    	    items.push_back(i);
    	    return i;
    	};
    
    	Container(){};
    	~Container(){};
        };
    
    template<int TSomeArg, int TSomeOtherArg>
    class ItemBase
    {
        template<typename TItem> friend class Container;
    
        private:
    	Container<ItemBase> *container;
    
        public:
    	ItemBase(){};
    	~ItemBase(){};
    };
    
    template<int TSomeArg, int TSomeOtherArg = 0>
    class Item : public ItemBase<TSomeArg, TSomeOtherArg>
    {
        public:
            Item(){};
    	~Item(){};
    };
    
    int main(int argc, char** argv)
    {
        // Beispiel für die Nutzung
        Container<Item<5>> c;
        Item<5> *i = c.newItem();    // Verursacht Fehler oben
    
        return 0;
    }
    

    Beim Compile bekomme ich folgenden Fehler:

    error C2440: '=' : cannot convert from 'Container<TItem> *const ' to 'Container<TItem> *'
    

    Selbst wenn ich anstatt

    Container<ItemBase> *container;
    

    ein

    Container<ItemBase<TSomeArg, TSomeOtherArg> *container;
    

    mache, klappt es nicht wie gewollt.

    Lasse ich die Vererbung über die ItemBase-Klasse weg, funktioniert alles wie es soll. Ich stehe gerade etwas auf dem Schlauch warum es hier zu einem Fehler kommt - müsste das nicht über den Polymorphismus funktionieren?

    Wäre echt dankbar wenn jemand hier etwas Licht ins Dunkeln bringen könnte. Falls etwas nicht ganz verständlich ist, werde ich versuchen es etwas zu erläutern.

    Danke für Antworten!

    Gruß
    PuerNoctis



  • Hallo,

    hast du in deinem Originalcode evtl. die newItem()-Funktion als 'const' deklariert (da dann der this-Zeiger ebenfalls innerhalb der Funktion als 'const' angesehen wird)?

    Und welchen Compiler nutzt du?



  • Hey Th69,

    leider nein, an das mit dem 'const' habe ich auch schon gedacht, aber die Methoden sind nicht als solche deklariert 😞

    Ich nutze den Visual C++ 2010.

    EDIT: Den Code aus meinem Post kann man übrigens 1:1 so hernehmen, im Prinzip ist das wirklich eine auf das grundlegende reduzierte Kopie des "Originalcodes" den ich gerade nutze.



  • gcc gibt eine hilfreichere Fehlermeldung:

    foo.cc: In Elementfunktion »TItem* Container<TItem>::newItem() [mit TItem = Item<5>]«:
    foo.cc:47:28:   instanziiert von hier
    foo.cc:13:9: Fehler: cannot convert »Container<Item<5> >*« to »Container<ItemBase<5, 0> >*« in assignment
    

    Was ja auch Sinn macht.



  • seldon schrieb:

    gcc gibt eine hilfreichere Fehlermeldung:

    foo.cc: In Elementfunktion »TItem* Container<TItem>::newItem() [mit TItem = Item<5>]«:
    foo.cc:47:28:   instanziiert von hier
    foo.cc:13:9: Fehler: cannot convert »Container<Item<5> >*« to »Container<ItemBase<5, 0> >*« in assignment
    

    Was ja auch Sinn macht.

    Und genau da bin ich mir eben nicht sicher... Wenn 'Item' ja von 'ItemBase' erbt, dann müsste er doch auch danach im Zuge des Polymorphismus casten können, oder etwa nicht? Was mich zu dem verwirrt, warum der GCC denkt, dass der this-Zeiger ein

    Container<Item<5> >*
    

    ist, und kein

    Container<Item<5, 0> >*
    

    was ja dann der Fehlermeldung nach funktionieren sollte, denn das zweite Templateargument IST ja vorhanden und wird auf 0 gedefaulted... Selbst wenn ich den Default-Wert von 'SomeOtherArg' weglasse und einen expliziten Cast reinjage

    i->container = static_cast<Container<TItem>*>(this);
    

    sagt mir der VC

    error C2440: '=' : cannot convert from 'Container<TItem> *' to 'Container<TItem> *'
    

    Das finde ich ja fast sogar ulkig.

    Hm, aber ich sehe schon durch seldon's Post, dass ich auch mit dem GCC versuchen sollte zu compilen, der ist da etwas hilfreicher was den Output angeht...

    EDIT: Achja, um's noch mal kurz zu erwähnen: Ich glaube das hat echt was mit der Vererbungshierarchie zu tun, denn wenn ich die Basisklasse 'ItemBase' weglasse und alle direkt in 'Item' reinstopfe, dann funktioniert das einwandfrei. Aber das kann ich leider nicht, ich brauche für das Konzept eine Basisklasse aus der ich individuelle Items für den Container machen kann... 😞



  • Du hast die Fehlermeldung nicht gelesen: die sagt nämlich, dass ein

    Container<Item<5> >
    

    kein

    Container<BaseItem<5, 0> >
    

    ist, was auch stimmt. Die beiden Container stehen in keiner Beziehung zueinander, nur weil ihre Template-Parameter in einer stehen.

    Es kommt jetzt darauf an, womit du dich zufrieden gibst.
    Z.B.

    template <int TOne, int TTwo, typename Item>
    struct BaseItem {
       Container<Item>* container;
    }
    
    template <int TOne, int TTwo = 0>
    struct Item : BaseItem<TOne, TTwo, Item<TOne, TTwo>> {
    }
    

    Hier kannst du dann allerdings nur bestimmte Items im BaseItem-Container speichern. Oder du sorgst dafür, dass ein Container<Item> auch ein Container<BaseItem> ist:

    template <typename TItem>
    struct Container : Container<TItem::base_t>
    {
     //...
     TItem* newItem () { TItem* i = new TItem; i->container = this; return i; }
    };
    
    template <>
    struct Container<void> { /* virtual ~Container() { } */ };
    
    template <typename TOne, typename TTwo>
    struct BaseItem {
      typedef void base_t;
      //...
    };
    
    template <typename TOne, typename TTwo=0>
    struct Item : BaseItem<TOne, TTwo>
    {
       typedef BaseItem<TOne, TTwo> base_t;
       //...
    }
    

    Aber warum soll ein Item überhaupt wissen müssen, in welchem Container es liegt? Was willst du eigentlich erreichen?



  • davie, danke für die Antwort!

    Zunächst zu Deinen Vorschlägen: Die erste Variante, den vollen Typ des Items direkt als weiteres Argument zu übergeben, reicht mir vollkommen, da in einem Container auch nur dieselben Items liegen sollen.

    Den ganzen Sinn und Zweck möchte ich jetzt aber nicht haarklein erklären. Ich bin sowieso immer etwas skeptisch alles bis ins kleinste Detail erklären zu müssen, obwohl das wesentliche vorliegt, bevor mal jemand mit einer vernünftigen Antwort kommt, anstatt immer zu sagen "Wieso willst Du das überhaupt machen, das macht doch gar keinen Sinn, und bibabo..."

    Aber um's ganz kurz zu "rechtfertigen":

    Der Container legt für seine Items global gewisse Eigenschaften fest, die von denen wiederum an bestimmten Punkten ausgewertet werden müssen. Anstatt jetzt jedesmal bei der Änderung einer Eigenschaft jedes Item im Vektor upzudaten, verknüpft der Container das Item beim Hinzufügen einfach mit sich selbst, und das Item ist in der Lage diese "Konfiguration" auszulesen.

    Der näheste Vergleich der mir dazu einfällt den ich kenne ist z.B. aus .NET das ADO.NET Framework. 'DataRows' werden 'DataTables' zugeordnet und intern nach oben verknüpft. Versucht man dieselbe Row an einer anderen Table zu attachen, fliegt eine Exception usw. So ähnlich läuft das mit diesem Container hier ab.

    Also danke für die Hilfe hier, damit komme ich soweit zu recht 🙂


Log in to reply