Innere Klasse wird bei Templatedeklaration nicht als Typ erkannt



  • Hi, ja das hab ich auch schon ausprobiert...

    also folgendes hilft zwar den ersten Fehler zu vermeiden:

    template<class T>
    const typename Matrix_2D<T>::Proxy Matrix_2D<T>::operator[](int x) const
    {
        return Matrix_2D<T>::Proxy(const_cast< Matrix_2D<T>& >(*this), x);
    }
    

    Aber bei folgendem:

    template<class T>
    typename Matrix_2D<T>::Proxy Matrix_2D<T>::operator[](int x)
    {
        return Matrix_2D<T>::Proxy(*this, x);
    }
    

    ..erhalte ich immer:

    147: error: dependent-name "Matrix_2D::Proxy" is parsed as a non-type, but instantiation yields a type
    147: note: say "typename Matrix_2D::Proxy" if a type is meant
    

    Ah ja?! So ein Spassvogel.. 😉 Das hab ich doch eig gemacht? Wie mach ich das bei der zweiten Deklaration, kann mir da jemand helfen? Geht das nur mit "const"? Warum?
    😕

    Danke fuer den ersten Hinweis und den Hinweis mit den Referencen, hab das auch nochmal nachgelesen.


  • Mod

    Fabeltier schrieb:

    Aber bei folgendem:

    template<class T>
    typename Matrix_2D<T>::Proxy Matrix_2D<T>::operator[](int x)
    {
        return Matrix_2D<T>::Proxy(*this, x);
    }
    

    ..erhalte ich immer:

    147: error: dependent-name "Matrix_2D::Proxy" is parsed as a non-type, but instantiation yields a type
    147: note: say "typename Matrix_2D::Proxy" if a type is meant
    

    Ah ja?! So ein Spassvogel.. 😉 Das hab ich doch eig gemacht?

    Nicht beim zweiten Mal, bei dem dieser Bezeichner auftritt. Das ist wesentlich, denn foo(argument-liste) ist entweder ein Cast, wenn foo ein Typ, oder ein Funktionsaufruf, wenn foo eine Funktion oder ein Objekt ist - aus dem Kontext kann der Compiler also nicht darauf schließen, dass Matrix_2D<T>::Proxy ein Typ ist, das musst du ihm folglich mitteilen (und zwar immer wieder). Zudem kannst du const/nicht-const Semantik mit Proxies nicht dadurch erreichen, das du den Rückgabewert mal const, mal nicht-const ausführst (nichts hindert den Aufrufer, deine const-Rückgabeproxie in einen non-const Proxie zu kopieren). Du brauchst hier zwei verschiedene Typen, wobei die nicht-const Version in die const-Version konvertierbar sein sollte. Als ganz analoges Anschauungsbeispiel können Iteratoren dienen. Mithin

    template<class T>
    typename Matrix_2D<T>::Proxy Matrix_2D<T>::operator[](int x)
    {
        return typename Matrix_2D<T>::Proxy(*this, x);
    }
    template<class T>
    typename Matrix_2D<T>::ConstProxy Matrix_2D<T>::operator[](int x) const
    {
        return typename Matrix_2D<T>::ConstProxy(*this, x);
    }
    


  • Super, Danke Euch - jetzt laeufts!!
    Meine Loesung ist nun folgende:

    template<class T>
    const typename Matrix_2D<T>::Proxy& Matrix_2D<T>::operator[](int x) const
    {
        return Matrix_2D<T>::Proxy(const_cast< Matrix_2D<T>& >(*this), x);
    }
    
    template<class T>
    typename Matrix_2D<T>::Proxy& Matrix_2D<T>::operator[](int x)
    {
        return typename Matrix_2D<T>::Proxy(*this, x);
    }
    

    😃



  • Fabeltier schrieb:

    Super, Danke Euch - jetzt laeufts!!
    Meine Loesung ist nun folgende:

    template<class T>
    const typename Matrix_2D<T>::Proxy& Matrix_2D<T>::operator[](int x) const
    {
        return Matrix_2D<T>::Proxy(const_cast< Matrix_2D<T>& >(*this), x);
    }
    
    template<class T>
    typename Matrix_2D<T>::Proxy& Matrix_2D<T>::operator[](int x)
    {
        return typename Matrix_2D<T>::Proxy(*this, x);
    }
    

    😃

    Mach das & da weg. Du erzeugst ein temporäres Objekt, und gibst dann einen Referenz darauf zurück.
    Das Objekt ist aber nach dem Funktionsaufruf futsch -> Nicht gut.


  • Administrator

    Tachyon schrieb:

    Mach das & da weg. Du erzeugst ein temporäres Objekt, und gibst dann einen Referenz darauf zurück.
    Das Objekt ist aber nach dem Funktionsaufruf futsch -> Nicht gut.

    Das wurde schon gesagt. Und es wurde auch gesagt, dass der const_cast eine ganz schlechte Lösung ist. Aber anscheinend sind das unwichtige Informationen 😉

    Und vielleicht, falls jemand doch noch auf diese Vorschläge hört, sollte man noch erwähnen, dass man besser einen Zeiger auf die Matrix an Proxy übergibt, statt einer Referenz. Einen Zeiger kann man kopieren, eine Referenz kann man nicht kopieren.
    Aja und vielleicht sollte man sogar die Proxy Klasse private machen. So ist die Klasse wirklich nur auf die Verwendung in diesem Zusammenhang beschränkt.

    Grüssli



  • Sry, die '&' waren Schreibfehler (cpy/paste).

    template<class T>
    const typename Matrix_2D<T>::Proxy Matrix_2D<T>::operator[](int x) const
    {
        return Matrix_2D<T>::Proxy(const_cast< Matrix_2D<T>& >(*this), x);
    }
    
    template<class T>
    typename Matrix_2D<T>::Proxy Matrix_2D<T>::operator[](int x)
    {
        return typename Matrix_2D<T>::Proxy(*this, x);
    }
    

    const_cast
    Das ist ein Problem, das mir noch nicht 100% klar ist. Ich habe mich hier an Scott Meyers orientiert, dort werden in einem Beispiel beide Versionen der operator[]() wie oben einmal mit und einmal ohne const implementiert. Da die Funktion als "xxx() const" definiert ist, brauch ich hier doch den const_cast um das wieder zu lockern (oder nicht?). Wozu brauche ich eine Const und eine ohne-Const Version (such das gerade nochmal im Buch, ich glaube da stand was..) Afaik ist jegliches casten ja immer eher schlecht, da es das eigentliche Konzept aufbricht, oder?!

    private
    ja hab ich schliesslich auch gemacht.

    Danke fuer die Hinweise!



  • Fabeltier schrieb:

    Da die Funktion als "xxx() const" definiert ist, brauch ich hier doch den const_cast um das wieder zu lockern (oder nicht?).

    Nein, nicht. Welchen Sinn hat es, eine Funktion const zu machen, aber das const mit const_cast wieder aufzubrechen?

    Fabeltier schrieb:

    Afaik ist jegliches casten ja immer eher schlecht, da es das eigentliche Konzept aufbricht, oder?!

    Natürlich nicht. static_cast ist meistens legitim. reinterpret_cast ist manchmal halt nötig. const_cast sollte man möglichst vermeiden, und dynamic_cast ist zwar teilweise praktisch, wird aber von vielen als Designfehler angesehen. Man sollte in C++ darauf achten, nicht den C-Cast mit den Klammern einzusetzen.


  • Administrator

    Fabeltier schrieb:

    Da die Funktion als "xxx() const" definiert ist, brauch ich hier doch den const_cast um das wieder zu lockern (oder nicht?).

    Wie Nexus gesagt hat, wieso etwas verschärfen, wenn du es danach wieder lockerst, das macht doch keinen Sinn.

    Fabeltier schrieb:

    Wozu brauche ich eine Const und eine ohne-Const Version (such das gerade nochmal im Buch, ich glaube da stand was..)

    Falls das Objekt konstant ist, sollte man nur lesen können, falls es nicht konstant ist, sollte man auch schreiben können. Darum geht es. Und camper hat wunderbar gezeigt, dass man dies über zwei Klassen erledigen kann, also eine Proxy und eine ConstProxy Klasse.
    Dann ist der const_cast nicht mehr nötig und alles funktioniert so, wie es eben sollte.

    Fabeltier schrieb:

    Afaik ist jegliches casten ja immer eher schlecht, da es das eigentliche Konzept aufbricht, oder?!

    Ich würde es nicht schlecht bezeichnen, aber man sollte immer überlegen, wenn man castet, ob es nicht anders gehen könnte.

    Fabeltier schrieb:

    private
    ja hab ich schliesslich auch gemacht.

    Jedenfalls nicht in dem uns gezeigten Beispiel.

    Nexus schrieb:

    Natürlich nicht. static_cast ist meistens legitim. reinterpret_cast ist manchmal halt nötig. const_cast sollte man möglichst vermeiden, und dynamic_cast ist zwar teilweise praktisch, wird aber von vielen als Designfehler angesehen.

    Da muss ich dir widersprechen Nexus. Bei jeglichem Cast sollte man darauf achten, ob da wirklich alles korrekt abläuft. Ich brauche seit Ewigkeiten keine Casts mehr, erst recht auch wegen dem Einsatz von Templates.
    Bei vielem ist es möglich ohne Casts klar zu kommen, man sollte sie wirklich nur äusserst selten verwenden und immer wenn man sie verwendet, sich überlegen, ob das nicht auch ohne gehen könnte.

    Grüssli



  • Dravere schrieb:

    Nexus schrieb:

    Natürlich nicht. static_cast ist meistens legitim. reinterpret_cast ist manchmal halt nötig. const_cast sollte man möglichst vermeiden, und dynamic_cast ist zwar teilweise praktisch, wird aber von vielen als Designfehler angesehen.

    Da muss ich dir widersprechen Nexus. Bei jeglichem Cast sollte man darauf achten, ob da wirklich alles korrekt abläuft. Ich brauche seit Ewigkeiten keine Casts mehr, erst recht auch wegen dem Einsatz von Templates.
    Bei vielem ist es möglich ohne Casts klar zu kommen, man sollte sie wirklich nur äusserst selten verwenden und immer wenn man sie verwendet, sich überlegen, ob das nicht auch ohne gehen könnte.

    Es ist vielleicht schon tendenziell so, dass man weniger Casts braucht, wenn man bereits länger programmiert. Zum Beispiel, weil static_cast hauptsächlich mit elementaren Typen verwendet wird, und man mit höherer Abstraktionsebene vielleicht weniger "direkt" programmiert.

    So pauschal würde ich dennoch nicht behaupten, es sei nur gut, so wenig Casts wie möglich zu verwenden. Es gibt einige Fälle, bei denen ich Casts einsetze.

    Nur mal ein Anfänger-Beispiel:

    double e = 3.25f;
    double f = 2.67f;
    
    float i = (e + f)/2;                     // (1)
    float j = static_cast<float>(e + f)/2.f; // (2)
    

    Der Cast wäre nicht unbedingt nötig. Trotzdem würde ich ihn an dieser Stelle schreiben, da ich erstens gerade sehe, dass von double zu float umgewandelt wird und zweitens die Warnung beseitige.

    Bei dynamic_cast habe ich eine ähnliche Meinung. Manchmal ist es eben recht praktisch, und für mich gibts da schlimmere Designfehler (was ist denn daran überhaupt so schlimm?). Ein Vermeiden des dynamic_cast s führt manchmal dazu, dass das gesamte Design geändert werden muss (ja, du wirst jetzt sagen, dass das Design schon von Anfang an schlecht war ;)).

    Man hat z.B. eine Basisklasse Base , und mehrere davon abgeleitete Klassen, darunter Derived .

    Base* Ptr;
    // ...
    Ptr = new Derived();
    Ptr->FunctionWhichOnlyExistsInDerived();                         // geht nicht.
    dynamic_cast<Derived*>(Ptr)->FunctionWhichOnlyExistsInDerived(); // geht.
    

    Wie macht man das sonst? Ich kann mir nur noch vorstellen, eine virtuelle Funktion zu schreiben, die dann innerhalb der Klasse die richtigen Funktionen aufruft. Aber das ist auch nicht immer erwünscht, manchmal will man von aussen je nach Klasse unterschiedlich zugreifen. Ist das bereits zwingend schlechtes Design?

    Bei const_cast teile ich deine Meinung uneingeschränkt, den habe ich eigentlich noch nie eingesetzt. reinterpret_cast kommt auch recht selten vor, und dann eher zu Testzwecken. Und es gehört für mich auch dazu, zu überprüfen, ob alles korrekt läuft, wenn ich caste.



  • Nexus schrieb:

    [...](was ist denn daran überhaupt so schlimm?)[...]

    Grundsätzlich kann man das schon machen, aber:
    Der Designbruch besteht darin, dass man damit die IsA-Beziehung aufbricht. Wenn Du ein abgeleitetes Objekt einem Basisklassenzeiger/Referenz zuweist, dann willst Du eigentlich damit sagen: An dieser Stelle will ich nur die Eigenschaften haben, welche die Basisklasse hat. Wenn Du nun das Basisklassenobjekt wieder hochcastest, dann bist Du eigentlich an einer Stelle, wo es besser gewesen wäre, gleich ein abgeleitetes Objekt zu haben, und nicht über ein Basisklassenobjekt zu gehen.



  • Tachyon schrieb:

    ...

    Exakt. Und das impiliziert ja, dass er zur Laufzeit bereits wissen muss, welches/e Objekte da in Frage kommen können. Das heisst, dass er vorher eine Typabfrage (in welcher Form auch immer) machen muss. Was ja schlussendlich (vom Prinzip her) auf das void gecaste zurückführt. 😉



  • Tachyon schrieb:

    Wenn Du nun das Basisklassenobjekt wieder hochcastest, dann bist Du eigentlich an einer Stelle, wo es besser gewesen wäre, gleich ein abgeleitetes Objekt zu haben, und nicht über ein Basisklassenobjekt zu gehen.

    Ja, aber manchmal hat man einen Basisklassenzeiger, um damit mehrere abgeleitete Klassen ansprechen zu können. Und wenn sich die einzelnen Klassen nicht nur in den Definitionen der virtuellen Funktionen unterscheiden, sondern beispielsweise noch weitere, spezifische Funktionen haben, möchte man vielleicht je nach Fall anders auf die Klasse reagieren. Und die spezifischen Eigenschaften einer Klasse kann man von aussen nur mit dynamic_cast modifizieren (man hat nur einen Basisklassenzeiger).



  • Nexus schrieb:

    Tachyon schrieb:

    Wenn Du nun das Basisklassenobjekt wieder hochcastest, dann bist Du eigentlich an einer Stelle, wo es besser gewesen wäre, gleich ein abgeleitetes Objekt zu haben, und nicht über ein Basisklassenobjekt zu gehen.

    Ja, aber manchmal hat man einen Basisklassenzeiger, um damit mehrere abgeleitete Klassen ansprechen zu können. Und wenn sich die einzelnen Klassen nicht nur in den Definitionen der virtuellen Funktionen unterscheiden, sondern beispielsweise noch weitere, spezifische Funktionen haben, möchte man vielleicht je nach Fall anders auf die Klasse reagieren. Und die spezifischen Eigenschaften einer Klasse kann man von aussen nur mit dynamic_cast modifizieren (man hat nur einen Basisklassenzeiger).

    Das bringt nichts. Wie drakon schon sagte, musst Du eh wissen, welches abgeleitete Objekt Du ansprechen willst. Sowas führt meist zu Funktionen in denen dann über irgendwelche Typabfragen das richtige Objekt zurechgecastet wird, und das ist nicht gerade sehr sauber. Da macht es mehr Sinn, z.B. überladene Funktionen für die verschiedenen in Frage kommenden Klassen zu machen, die die Spezialfunktionen darin aufzurufen.



  • drakon schrieb:

    Exakt. Und das impiliziert ja, dass er zur Laufzeit bereits wissen muss, welches/e Objekte da in Frage kommen können. Das heisst, dass er vorher eine Typabfrage (in welcher Form auch immer) machen muss. Was ja schlussendlich (vom Prinzip her) auf das void gecaste zurückführt. 😉

    Stimmt, da hast du Recht. Wahrscheinlich wird mein Beispiel eh auf eine hässliche Fallunterscheidung hinauslaufen.

    Was aber, wenn man von aussen (von mir aus von einer anderen Klasse) auf eine spezifische Funktion zugreifen möchte, während dort nur der Basisklassenzeiger bekannt ist?



  • Nexus schrieb:

    Was aber, wenn man von aussen (von mir aus von einer anderen Klasse) auf eine spezifische Funktion zugreifen möchte, während dort nur der Basisklassenzeiger bekannt ist?

    Dann hast Du ein gutes Beispiel für schlechtes Design. 😉


  • Administrator

    Nexus schrieb:

    Nur mal ein Anfänger-Beispiel:

    double e = 3.25f;
    double f = 2.67f;
    	
    float i = (e + f)/2;                     // (1)
    float j = static_cast<float>(e + f)/2.f; // (2)
    

    Und wieso nicht gleich:

    double e = 3.25;
    double f = 2.67;
    
    double i = (e + f)/2.0;
    double j = (e + f)/2.0;
    

    Auch wenn du sagst, das sei nur ein Beispiel gewesen, aber genau darauf läuft es hinaus. Ich benutze nur ein static_cast , bei solchen Dingen, wenn es irgendeine Inkonsistenz zwischen zwei Bibliotheken gibt.
    Bei deiner eigenen Bibliothek oder Programm, sollte so eine Inkonsistenz erst gar nicht auftauchen 😉

    Nexus schrieb:

    Bei dynamic_cast habe ich eine ähnliche Meinung. Manchmal ist es eben recht praktisch, ...

    Um deine Designfehler zu behben? 🙂

    Nexus schrieb:

    ... und für mich gibts da schlimmere Designfehler (was ist denn daran überhaupt so schlimm?).

    Schlimmere gibt es natürlich schon.

    Nexus schrieb:

    Ein Vermeiden des dynamic_cast s führt manchmal dazu, dass das gesamte Design geändert werden muss (ja, du wirst jetzt sagen, dass das Design schon von Anfang an schlecht war ;)).

    Genau, es war von anfang an nicht korrekt durchdacht. Gut, da kann man dann manchmal nicht nochmals alles auf den Kopf stellen. Aber theoretisch sollte man 😉

    Für das Restliche, siehe Tachyons Erklärung.

    Nexus schrieb:

    Ja, aber manchmal hat man einen Basisklassenzeiger, um damit mehrere abgeleitete Klassen ansprechen zu können. Und wenn sich die einzelnen Klassen nicht nur in den Definitionen der virtuellen Funktionen unterscheiden, sondern beispielsweise noch weitere, spezifische Funktionen haben, möchte man vielleicht je nach Fall anders auf die Klasse reagieren. Und die spezifischen Eigenschaften einer Klasse kann man von aussen nur mit dynamic_cast modifizieren (man hat nur einen Basisklassenzeiger).

    Wieso hast du nur einen Basisklassenzeiger? Wieso übergibst du nicht gleich die abgeleiteten Klassen? Du kannst ja Funktionen überladen. Allenfalls kannst du sowas machen:

    class Base
    {
    public:
      void base_method();
    };
    
    class One : public Base
    {
    public:
      void one_method();
    };
    
    class Two : public Base
    {
    public:
      void two_method();
    };
    
    // Dein vorgehen:
    void foo(Base* base)
    {
      base->base_method();
    
      One* one = dynamic_cast<One*>(base);
    
      if(one)
      { one->one_method(); }
    }
    
    // Mein vorgehen:
    void foo(Base* base)
    {
      base->base_method();
    }
    
    void foo(One* one)
    {
      one->base_method();
      one->one_method();
    }
    

    Grüssli

    PS: SAGT MAL, WAS POSTET HIER SO SCHNELL??? Jedesmall wenn ich auf Vorschau geklickt habe, waren da 2-3 weiter Posts. Geht gefälligst arbeiten! 😃



  • Tachyon schrieb:

    Das bringt nichts. Wie drakon schon sagte, musst Du eh wissen, welches abgeleitete Objekt Du ansprechen willst. Sowas führt meist zu Funktionen in denen dann über irgendwelche Typabfragen das richtige Objekt zurechgecastet wird, und das ist nicht gerade sehr sauber.

    Ja, hab ich eben auch gerade gemerkt. 😉

    Tachyon schrieb:

    Da macht es mehr Sinn, z.B. überladene Funktionen für die verschiedenen in Frage kommenden Klassen zu machen, die die Spezialfunktionen darin aufzurufen.

    Ja, das war ja meine Alternative 2 Posts vorher. Gibt es nicht Fälle, wo man unbedingt von aussen direkt Spezifisches ansprechen will? Weil beispielsweise die aufgerufene Methode je nach Klasse andere Parameter benötigt, was durch eine einheitliche Schnittstelle über eine virtuelle Funktion nicht zu realisieren wäre?



  • ***** OT-Ticker *** OT-Ticker *** OT-Ticker *****

    Dravere schrieb:

    PS: SAGT MAL, WAS POSTET HIER SO SCHNELL??? Jedesmall wenn ich auf Vorschau geklickt habe, waren da 2-3 weiter Posts. Geht gefälligst arbeiten! 😃

    Eigentlich müsste ich das wirklich dringend, aber ich hab hier im Moment echt gräßliches Dokuzeugs. Das ist so ätzend, da verbringt man lieber Zeit mit dem Verbeiwürgen von hartem Stuhl an schmerzenden Hämorrhoiden, als mit dieser Arbeit.
    ***** OT-Ticker *** OT-Ticker *** OT-Ticker *****

    Nexus schrieb:

    Ja, das war ja meine Alternative 2 Posts vorher. Gibt es nicht Fälle, wo man unbedingt von aussen direkt Spezifisches ansprechen will? Weil beispielsweise die aufgerufene Methode je nach Klasse andere Parameter benötigt, was durch eine einheitliche Schnittstelle über eine virtuelle Funktion nicht zu realisieren wäre?

    "Andere Parameter benötigen" und "einheitliche Schnittstelle" beisst sich ja irgendwie schon, oder? 😉



  • Dravere schrieb:

    Um deine Designfehler zu behben? 🙂

    Ähm... Bitte keine Vorurteile. :p

    Dravere schrieb:

    Auch wenn du sagst, das sei nur ein Beispiel gewesen, aber genau darauf läuft es hinaus.

    Grr, man findet immer etwas. 😉
    Naja, das Beispiel sollte das Prinzip erläutern, von daher läuft es nicht genau darauf hinaus. Von mir aus kann man 2.f durch eine float -Variable ersetzen.

    Dravere schrieb:

    Ich benutze nur ein static_cast, bei solchen Dingen, wenn es irgendeine Inkonsistenz zwischen zwei Bibliotheken gibt.

    Teilweise ist man aber auch selber gezwungen, eine Konvertierung durchzuführen (sei es auch nur, um eine Warnung zu verhindern). Oder, um die Genauigkeit des Resultats anzupassen (z.B. Division zweier Integer).

    Dravere schrieb:

    PS: SAGT MAL, WAS POSTET HIER SO SCHNELL??? Jedesmall wenn ich auf Vorschau geklickt habe, waren da 2-3 weiter Posts. Geht gefälligst arbeiten! 😃

    Habe ich mich auch gefragt. Meine Antworten sind immer auf etwa zwei Posts vorher bezogen...

    Tachyon schrieb:

    "Andere Parameter benötigen" und "einheitliche Schnittstelle" beisst sich ja irgendwie schon, oder? 😉

    Ja eben, deshalb ist auch die Schnittstelle unterschiedlich, wodurch ein dynamic_cast erforderlich ist. Ich weiss, ich habe nicht die besten Beispiele, aber so ein Fall könnte doch schon mal vorkommen...



  • Nexus schrieb:

    Ja eben, deshalb ist auch die Schnittstelle unterschiedlich, wodurch ein dynamic_cast erforderlich ist. Ich weiss, ich habe nicht die besten Beispiele, aber so ein Fall könnte doch schon mal vorkommen...

    Das heisst, Du willst an einer Stelle eine einheitliche Schnittstelle haben. Diese ist die Basisklasse. Dann fällt Dir aber auf, dass die Schnittstelle wohl doch nicht so einheitlich sein soll, und Du castest das, was über die einheitliche Schnittstelle reinkam wieder auf nicht einheitliche, abgeleitete Objekte zurück. Dann braucht man abere eben gerade auch keine einheitliche Schnittstelle.

    Ja, sowas kann vorkommen, aber nur wenn Du Dir vorher nicht ausreichend Gedanken dazu gemacht hast.
    Ich hab dynamic_cast<>, glaube ich, erst einmal gebraucht. Ich hatte mir nicht genug Gedanken über das Design gemacht, und einfach drauf losprogrammiert. Da war schon so viel Code zurechtgerickelt, dass ich fürs Neuschreiben einen auf den Deckel bekommen hätte.


Anmelden zum Antworten