templates mal wieder



  • Ich habe eine Klasse "CComponent", wobei "CComponent" wiederum eine Template-Methode besitzt:

    class CComponent {
    public:
      ...
      template<typename value_type>
      void InsertProperty(const std::string &a_property_name) const;
      ,,,
    };
    
    template<typename value_type>
    void CComponent::InsertProperty(const std::string &a_name) {
    ...
    }
    

    Möchte ich nun die Methode aus einer Template-Funktion aufrufen, bekomme ich Compilerfehler.

    folgender Code funktioniert:

    void test(CComponent &a_component) {
      a_component->InsertProperty<int>("anzahl");
    }
    

    Dagegen funktioniert dieser Code nicht:

    template<typename a_type>
    void test(CComponent &a_component) {
      a_component->InsertProperty<int>("anzahl");
    }
    

    Gibt es hierfür eine Lösung oder einen Workaround?

    Danke!



  • folgender Code funktioniert:

    void test(CComponent &a_component) {
    a_component->InsertProperty<int>("anzahl");
    }

    Ne. Mit Sicherheit nicht. Es sei denn du hast den op-> überladen und verheimlichst uns das.

    Sehen wir mal davon und von dem fehlenden const bei der Methodendefinition ab, dann sehe ich hier eigentlich kein Problem. Was genau passiert denn? Und könnest du mal bitte ein Beispiel posten, dass sich kompilieren lässt bzw. den Fehler zeigt?

    [ Dieser Beitrag wurde am 03.04.2003 um 23:20 Uhr von HumeSikkins editiert. ]



  • Ok, nun in korrigierter Form:

    folgender Code funktioniert:

    #include <string>
    
    class CComponent {
    public:
      CComponent();
      ~CComponent();
    
      template<typename value_type>
      void InsertProperty(const std::string &a_property_name) const;
    };
    
    template<typename value_type>
    void CComponent::InsertProperty(const std::string &a_name) const {
    }
    
    void foo(CComponent &a_component) {
      a_component.InsertProperty<int>("anzahl");
    }
    

    und foo mit Template nicht:

    #include <string>
    
    class CComponent {
    public:
      CComponent();
      ~CComponent();
    
      template<typename value_type>
      void InsertProperty(const std::string &a_property_name) const;
    };
    
    template<typename value_type>
    void CComponent::InsertProperty(const std::string &a_name) const {
    }
    
    template<typename a_type>
    void foo(CComponent &a_component) {
      a_component.InsertProperty<int>("anzahl");
    }
    

    warum bzw. wie kann ich die 2. Version es lauffähig machen?

    [ Dieser Beitrag wurde am 04.04.2003 um 01:24 Uhr von wischmop2 editiert. ]



  • Hallo,
    wenn du foo im zweiten Fall mit
    foo<Type>(c);
    (wobei c vom Typ CComponent) aufrufst und das ganze nicht funktioniert, handelt es sich um einen Fehler deines Compilers.
    Wie ist die Fehlermeldung? Was stört ihn?



  • Ich finde es ja auch so unerklärlich, dass es plötzlich nicht funktioniert, nur wenn man die Funktion foo als Templatefunktion umbaut. Aber an einen Compilerfehler habe ich mich bisher nicht getraut zu denken. Meistens bin ich nämlich immer zu blöd, und nicht der Compiler. Aber hier scheint es sich vielleicht wirklich um einen Compilerfehler zu handeln...

    So, gcc 3.2 meldet folgenden (nicht sehr vielsagenden) Fehler in der Zeile

    a_component.InsertProperty<int>("anzahl");
    

    In function 'void foo(CComponent&)':
    parse error before '>' token

    Ist möglicherweise gar nichtmal ein "Compilerfehler" im eigentlichen Sinn, sondern intern ein Fehler vom Parser? Aber dann müsste es doch ne Möglichkeit geben, den Code etwas umzubauen, so dass der Parser wieder alles erkennt...?



  • Hallo,
    bist du sicher, dass der Code so aussieht, wie du ihn gepostet hast?

    Nicht vielleicht eher so?

    template<typename a_type>
    void foo(a_type &a_component) {
      a_component.InsertProperty<int>("anzahl");
    }
    

    oder so?

    template<typename a_type, class T>
    void foo(CComponent<T> &a_component) {
      a_component.InsertProperty<int>("anzahl");
    }
    

    In beiden Fällen wäre der Fehler nämlich klar und tatsächlich deiner.
    Der Parameter a_component wäre in beiden Fällen ein abhängiger Name. Und damit würde InsertProperty<int> nicht mehr als Aufruf einer Templatemethode des Objekts a_component interpretiert sondern als Vergleich zwischen a_component.InsertProperty und int. Das ergibt einen Fehler weil der Typ 'int' nicht in einfach in einem Vergleich auftauchen darf und weil der Rest '>("anzahl")' dann auch keinen Sinn mehr ergibt.

    Die Lösung geht so, wie sie bei abhängigen Namen immer geht. Explizit Qualifizieren. In diesem Fall nicht mit typename sondern mit template:

    a_component.template InsertProperty<int>("anzahl");
    

    Aber eigentlich kann es daran ja nicht liegen, da dein Code ja so aussieht wie der den du gepostet hast 😃

    [ Dieser Beitrag wurde am 04.04.2003 um 16:13 Uhr von HumeSikkins editiert. ]



  • Nein, ich schwör' Dir 😉 , der Code ist so, wie ich ihn gepostet habe. Nach Deiner ersten Antwort habe extra ein kleines Testprogramm geschrieben und per Cut&Paste hier gepostet.
    Deshalb verstehe ich das ja auch nicht, warum die Funktion "foo" plötzlich fehlerhaft sein soll, wenn ich sie als Templatefunktion implementiere, obwohl ich von dem Templateargument nie gebrauch mache 😕

    Naja, wenigstens ist es tröstlich zu wissen, dass ich nicht wieder der Idiot bin, sondern diesmal der Compiler.
    Sollte irgend wer noch eine Idee habe, dann bitte her damit. Ich versuche mich derweil an einem Workaround, mit ungewissem Ausgang...

    @HumeSikkins: Trotzdem vielen Dank!



  • wischmop2: Bitte gib uns endlich einen vollständigen Code, den du versuchst zu kompilieren und wir lösen den Fehler auf.



  • @wischmop2
    Nur so als Hinweis. Wenn du das template (erklärt in meinem vorherigen Beitrag) hinzufügst, ist auch der gcc 3.2 zufrieden.
    Das ist allerdings in meinen Augen ein Compiler-Bug, da der Parameter nicht vom Templateparameter abängig ist.

    Also:

    template<typename a_type>
    void foo(CComponent &a_component) {
      a_component.template InsertProperty<int>("anzahl");
    }
    

    kompiliert mein gcc 3.2 ganz brav.

    [ Dieser Beitrag wurde am 04.04.2003 um 16:53 Uhr von HumeSikkins editiert. ]



  • @<Könner>: Was willst Du? Ich habe den vollständigen Code gepostet? Ok, es fehlt noch etwas:

    int main(int argc, char *argv[]) {
      return 0;
    }
    

    @HumeSikkins:
    Tatsache! Habe jetzt, wie Du ja schreibst, dass es funktioniert, meinen Code

    template<typename a_type>
    void foo(CComponent &a_component) {
      a_component.InsertProperty<int>("anzahl");
    }
    

    durch Deinen ersetzt (bzw. um das "template " ergänzt, und es läuft[/cpp]

    template<typename a_type>
    void foo(CComponent &a_component) {
      a_component.template InsertProperty<int>("anzahl");
    }
    

    Deshalb: VIELEN VIELEN DAAAANK!!!!!!
    Eigentlich bin ich damit ja jetzt sau glücklich und könnte mich zufrieden geben. Aber kannst Du mir vielleicht kurz erklären, was das ".template" bedeutet? Ich habe sowas noch nie vorher gesehen. Wenn es eine zu komplizierte oder zu lange Abhandlung wird, ist es nicht notwendig, dann lese ich's selbst irgendwo nach. Aber wenn's vielleicht ne ganz einfache Sache ist...?

    [ Dieser Beitrag wurde am 04.04.2003 um 20:05 Uhr von wischmop2 editiert. ]



  • @HumeSikkins: Ich darf dich und auch wischmop darauf aufmerksam machen, dass das Verhalten vom gcc völlig standardkonform ist 🙂
    Tatsächlich soll jeder standardkonforme Compiler sich beschweren, wenn mitten in einem Funktionsaufruf ein < Operator eingeschoben wird, dem ein typename (in dem Fall int) folgt. Man muss hier explizit qualifizieren.
    Da ich den Standard im Moment (und auch in den nächsten Momenten) nicht griffbereit habe, bzw. keine Lust da alles zusammenzusuchen, hab ich folgendes Wort-für-Wort aus dem Buch "Die C++ Programmiersprache" entnommen, von unserem lieben Björn (:D):
    (bitte nicht vom Namen 'Allokator' verwirren lassen)

    template <class Allokator> void f(Allokator &m) 
    { 
       int* p1 = m.get_new<int>(); //Syntaxfehler: int nach Operator kleiner-als
       int* p2 = m.template get_new<int>(); //explizite Qualifizierung
       //...
       m.release(p1); //Template-Argument hergeleitet: keine explizite Qualifizierung notwendig
       m.release(p2);
    }
    

    Noch sein (Bjarnes) Kommentar dazu:

    Die explizite Qualifizierung von get_new() ist notwendig, da der Template-Parameter nicht hergeleitet werden kann. In dem Fall muss das Präfix template verwendet werden, um den Compiler (und den menschlichen Leser) zu informieren, dass get_new ein Element-Template und eine explizite Qualifizierung mit dem gewünschten Datentyp möglich ist. Ohne die Qualifizierung mit template würde man einen Syntaxfehler erhalten, da < als Kleiner-als-Operator betrachtet wird. Der Bedarf für eine Qualifizierung mit template ist selten, da die meisten Template-Parameter hergeleitet werden

    Es liegt also daran, dass template Parameter normalerweise "normal" hergeleitet werden können, was bei xxx(void) funktionen nicht so einfach ist 🙂
    Hoffe ich hab da schnell was klargestellt 🙂
    so long, and thanks for the fish



  • @fisch:
    Mir hat's was gebracht und das ".template " ist also nix besonderes, sondern nur so ein Schlag auf den Hinterkopf des Compilers 😃 (oder so ähnlich...)

    [ Dieser Beitrag wurde am 04.04.2003 um 20:35 Uhr von wischmop2 editiert. ]



  • @fisch
    Du irrst dich und missinterpretierst das Beispiel und B. Stroustrup.

    Schau:

    template <class Allokator> void f(Allokator &m)

    Hier ist m *abhängig* vom Templateparameter Allokator. Deshalb kann
    der Compiler in der ersten Prüfungsphase (bevor das Template instanziiert wird) nicht feststellen was get_new ist. Er *nimmt an*, dass es sich hier um ein Member hält. Nicht um ein *Member-Template*.

    Nun schau aber hier:

    template<typename a_type>
    void foo(CComponent &a_component)

    a_component ist *nicht* abhängig vom Templateparameter a_type. a_component ist vom Typ CComponent unabhängig davon, was a_type ist.
    Der Compiler kann und muss in die Definition von CComponent schauen und dann erkennen, dass es sich bei get_new um ein Member-Template hält.



  • Aber kannst Du mir vielleicht kurz erklären, was das ".template" bedeutet?

    Diese Frage wurde mir heute auch bereits per Mail gestellt.
    Ich poste deswegen einfach mal meine Antwort. Es handelt sich hier um eine Mail, also entschuldigt bitte die etwas saloppen ausdrücke:

    Betrachten wir die Funktion:
    template <class AType>
    void Func(AType obj)
    {
    obj.Func();
    obj.i = 22;
    obj.anotherFunc<int>();
    }

    Die Funktion (wie jede Templatefunktion) wird vom Compiler in zwei Phasen betrachtet. In der ersten betrachtet er sie ohne den konkreten Type von AType zu
    kennen. Die zweite Phase beginnt, wenn er einen tatsächlichen Aufruf der Funktion sieht:
    class Foo {...};
    Foo f;
    Func(f);

    In der zweiten Phase weiß der Compiler alles was er braucht. Die Übersetzung der Templatefunktion unterscheidet sich nicht mehr von der einer normalen Funktion.

    Vor Phase zwei hat der Stroustrup aber Phase 1 gesetzt 🙂
    In der ersten Phase weiß der Compiler nichts über den konkreten Typ von AType.
    Er schaut nur ob der Code sinn macht. Er prüft hier nicht, ob obj eine Methode Func hat oder ob obj.i kompatibel zu 22 ist.
    Er prüft nur ob die Ausdrücke rein syntaktisch gültiges C++ sind.

    obj.Func();
    Klar. Ist gültiges C++. In der mitte steht ein Punkt, links etwas, dass ein Objekt sein könnte, rechts etwas das eine Member-Funktion sein könnte und am Ende ein Semikolon. Ok.

    obj.i = 22;
    Auch ok. Selbes Prinzip. Der Compiler wird i als ein *Member* von obj interpretieren dem etwas zugewiesen wird. Wie gesagt, er prüft hier noch nicht, ob die Zuweisung legal ist. Das passiert erst in der zweiten Phase, wenn also der *konkrete* Typ von obj klar ist.

    Nun kommen wir zum Problem:

    obj.anotherFunc<int>();
    Das ergibt erstmal kein gültiges C++. Bedenke. Der Compiler ist kein Mensch. Er kann nicht wissen, was wir uns bei dem Code denken. Er kann nur
    simple Regeln befolgen. Und eine (die wir oben schon zweimal angewendet haben) lautet davon mehr oder weniger: "Interpretiere alles rechts von einem Punkt
    als Member des Objekts links vom Punkt".
    Das macht der Compiler:
    obj ist das Objekt. anotherFunc das Element. Und huch. Was kommt da? Ein "kleiner-als" Zeichen. Also ein Vergleich zwischen
    obj.anotherFunc und dem was als nächstes kommt. Bis hier hin nicht schlecht. Was kommt als nächstes? int!
    Peng. int ist kein gültiges Argument für den zweistelligen operator <. Niemals. Völlig egal was obj ist.

    Das Problem ist also, dass du mit anotherFunc<int> das *Member-Template* (besser die Member-Template-Funktion) von obj meinst, der
    Compiler dies aber als ein Vergleich zwischen dem *Member* anotherFunc und int interpretiert.
    Du musst dem Compiler also mitteilen, dass er anotherFunc<int> als template lesen soll.
    Und das machst du durch die Qualifizierung mit template.
    Ergibt:
    obj.template anotherFunc<int>();

    Fertig 🙂

    Gruß
    Hume



  • Original erstellt von HumeSikkins:
    a_component ist *nicht* abhängig vom Templateparameter a_type. a_component ist vom Typ CComponent unabhängig davon, was a_type ist.
    Der Compiler kann und muss in die Definition von CComponent schauen und dann erkennen, dass es sich bei get_new um ein Member-Template hält.

    ach gottchen ich hab gedacht das ist ein Templateparameter, naja, bin schon etwas ko 🙂
    also ist es doch ein bug vom gcc...
    wiedermal hast du deine überlegenheit bewiesen 😃
    und wenn du das nächste mal sagst, du beherrschtest nur eine Teilmenge von c++, dann sag bitte dazu, dass die sehr groß ist und dass du sie sehr, sehr gut beherrscht 😃 *fleh*



  • @Hume: Super Erklärung! Gerafft!


Anmelden zum Antworten