Curious Mixins



  • @sewing sagte in Curious Mixins:

    das ich mich gefragt habe, ob ein solcher Fehler im Syntax tatsächlich ein Fehler ist, oder an meinem Compiler liegt (in der Errata des von mir genannten Buches taucht er nicht auf)

    Nachdem es kein aktueller Compiler frisst und zumindest das "class" ganz klar fehlt...

    Meine Frage wäre noch, ob der Barton-Nackman Trick es ermöglicht, den static_cast beim crtp zu umgehen

    Ja. Wenn es geht, dann geht es. Wenn nicht, dann nicht.
    Ich meine, ernsthaft, was ist deine Frage? Bei friend Funktionen ja, bei Memberfunktionen natürlich nicht weil der "Barton-Nackman Trick" da generell nicht funktioniert.



  • Bin etwas verwirrt, wieso ist denn bei folgendem Ausschnitt kein cast nötig?

    template <typename Derived>
    struct Comparisons {
        friend bool operator!= (const Derived& lhs, const Derived& rhs) { return !(lhs == rhs); }
    }
    

    bei der Variante hier aber doch

    template <typename Derived>
    struct Comparisons
    {
    };
    
    
    template <typename Derived>
    bool operator!=(const Comparisons<Derived>& lhs, const Comparisons<Derived>& rhs)
    {
        const Derived& d1 = static_cast<const Derived&>(lhs);
        const Derived& d2 = static_cast<const Derived&>(rhs);
    
        return !(d1 == d2);
    }
    
    


  • @sewing sagte in Curious Mixins:

    Bin etwas verwirrt, ...

    Anscheinend ja, wie kommst du auf die Idee const Comparisons<Derived>& in const Derived& casten zu wollen (s.a. Ideone-Code)?



  • wird hier so gemacht



  • @th69 sagte in Curious Mixins:

    @sewing sagte in Curious Mixins:

    Bin etwas verwirrt, ...

    Anscheinend ja, wie kommst du auf die Idee const Comparisons<Derived>& in const Derived& casten zu wollen (s.a. Ideone-Code)?

    wieso denn nicht? sind doch durch Inheritance in Beziehung zueinander



  • OK, du hattest aber nicht dazugeschrieben, daß dieser Code bezogen auf CRTP ist.
    Du mußt dann natürlich eine Klasse/Struktur haben, die das auch anwendet, z.B.:

    struct Test : Comparisons<Test>
    

    s.a. geänderter Ideone-Code

    Und der static_cast ist jetzt erforderlich, weil es ja kompilertechnisch keine direkte Ableitung gibt und daher beim CRTP immer gecastet werden muß.

    Hätte man aber eine direkte Ableitung:

    template <typename Derived>
    struct Comparisons : Derived
    {
    };
    

    so wäre dann keinstatic_cast (oder jeder andere Cast) mehr nötig.



  • danke für deine Antwort. Bin allerdings immer noch verwirrt.

    friend function deklarationen inenrhalb von Klassen erfüllen meines Wissens nach drei Zwecke:

    1. Symmetrisierung von Funktionsaufrufen (zB. Operatoren etc.), da die einzige Möglichkeit free functions innerhalb eines class scopes zu definieren ist, sie als Friend zu markieren
    2. Zugriff auf private member einer Klasse
    3. Barton-Nackman trick mittels ADL (damals nützlicher als heute, wo function templates überladen werden können)

    jetzt finde ich im Netz verschiedene Beispiele des CRTP

    mal wird ganz klassisch innerhalb des Basisklassen Templates mittels eines static_casts des this-pointers auf den derived type eine member-Function der abgeleiteten Klasse verwendet.

    Häufiger aber sehe ich einen Syntax, den ich noch nicht ganz durchdrungen habe:

    template<typename Derived>
    class Comparisons {
     public:
     ...
      friend bool operator!= (Comparisons<Derived> const& a, Comparisons<Derived> const& b){
        return !(a == b);
      }
    };
    

    1. Worin besteht hier der Unterschied, wenn ich stattdessen schreibe

    ...
      friend bool operator== (Derived const& a,  Derived const& b){
        return !(a == b);
      }
    

    2. Bezogen auf dein letztes Snippet:

    OK, du hattest aber nicht dazugeschrieben, daß dieser Code bezogen auf CRTP ist.
    Du mußt dann natürlich eine Klasse/Struktur haben, die das auch anwendet, z.B.:

    struct Test : Comparisons<Test>
    

    s.a. geänderter Ideone-Code

    Und der static_cast ist jetzt erforderlich, weil es ja kompilertechnisch keine direkte Ableitung gibt und daher beim CRTP immer gecastet werden muß.

    Hätte man aber eine direkte Ableitung:

    template <typename Derived>
    struct Comparisons : Derived
    {
    };
    

    so wäre dann keinstatic_cast (oder jeder andere Cast) mehr nötig.

    Ich sehe aber eigentlich in jedem Beispiel zu CRTP Code, in dem mittels static_cast member functions in der abgeleiteten Klasse aufgerufen werden. Wann und unter welchen Vorraussetzungen lässt sich das umgehen?
    Ich kann doch dann beispielsweise free functions derart definieren

      friend bool foo(const Derived &lhs) {
        lhs.bar();
        return true;
      }
    

    In C++ Templates: The Complete Guide steht dazu

    In modern C++, the only advantages to defining a friend function in a class
    template over simply defining an ordinary function template are syntactic: friend
    function definitions have access to the private and protected members of their
    enclosing class and don’t need to restate all of the template parameters of
    enclosing class templates.
    

    Stimmt das wirklich? Free function templates würden ja dann für alle Typen funktionieren, die entsprechenede Operatoren definieren, wozu braucht man dann noch CRTP?
    Im Gegensatz dazu würde die friend funktion, die beim kompilieren als free non-template in den global namescope injiziert wird, ja als parameter genau den Typ 'Derived' haben, das Ergebnis wäre also bei Instanziierung mit Typ Test beispielweise

    bool operator!= (Test const& a, Test const& b){
        return !(a == b);
    

    und diese definiton ist ja ein Unterschied zu der absolut generischen Variante, oder übersehe ich hier was?

    Als dritte Option kann man definieren

    template <typename Derived>
    bool operator!=(const Comparisons<Derived>& o1, const Comparisons<Derived>& o2)
    {
        const Derived& d1 = static_cast<const Derived&>(o1);
        const Derived& d2 = static_cast<const Derived&>(o2);
    
          return !(a == b);
    }
    

    Hat dann allerdings wieder den Cast drin.

    3. Wieso wirft mir der Compiler beim Überladen einer Funktion in der CRTP Basisklasse den Fehler

    " redefinition of ‘void process()*"

      friend bool process(const DerivedT &lhs) {
       lhs.bar();
      return true;
      }
    
      friend void process() {
      }
    

    Ich überlade doch über ein funktionparameter, das müsste doch erlaubt sein

    4. Muss die friend function in der template base blass auch definiert werden?
    Oder kann ich sie dort auch nur deklarieren und außerhalb der Klasse definieren?
    Ich wüsste gerade nicht, wie das funktionieren sollte,da die funktion selbst ja nicht templatisiert ist...



  • @sewing sagte in Curious Mixins:

    1. Wieso wirft mir der Compiler beim Überladen einer Funktion in der CRTP Basisklasse den Fehler
      " redefinition of ‘void process()*"
      friend bool process(const DerivedT &lhs) {
      lhs.bar();
      return true;
      }

    friend void process() {
    }

    Ich überlade doch über ein funktionparameter, das müsste doch erlaubt sein

    Weil du friend nicht verstanden hast.
    Mit friend definierst du da keine Klassenmember. Du definierst damit freie Funktionen. Und wenn mehrmals eine freie Funktion void process() definiert wird, dann ist das natürlich ein Fehler.



  • Hat dich das friend jetzt so verwirrt? Ich mache das aus Gewohnheit, weil Operatoren meistens direkt auf private Member zugreifen.
    Daher hier noch mal geänderter Ideone-Code ohne friend.

    Hast du denn noch Fragen zu diesem CRTP-Code?

    Edit:
    Und du kannst natürlich auch friend-Funktionen außerhalb definieren:

    struct Test : Comparisons<Test>
    {
        friend bool operator==(const Test& t1, const Test& t2);
    };
    
    bool operator==(const Test& t1, const Test& t2)
    {
        return true;
    }
    

    Noch ein Edit:
    Du meinst ja innerhalb eines Templates, dann benutze für die friend-Funktion einen eigenen Template-Parameter:

    template<typename Derived>
    class Comparisons {
     public:
      template <typename T>
      friend bool operator!=(Comparisons<T> const& a, Comparisons<T> const& b);
    };
    
    template <typename Derived>
    bool operator!=(const Comparisons<Derived>& lhs, const Comparisons<Derived>& rhs)
    {
        const Derived& d1 = static_cast<const Derived&>(lhs);
        const Derived& d2 = static_cast<const Derived&>(rhs);
    
        return !(d1 == d2);
    }
    

    s. noch ein Ideone-Code



  • @hustbaer sagte in Curious Mixins:

    @sewing sagte in Curious Mixins:

    1. Wieso wirft mir der Compiler beim Überladen einer Funktion in der CRTP Basisklasse den Fehler
      " redefinition of ‘void process()*"
      friend bool process(const DerivedT &lhs) {
      lhs.bar();
      return true;
      }

    friend void process() {
    }

    Ich überlade doch über ein funktionparameter, das müsste doch erlaubt sein

    Weil du friend nicht verstanden hast.
    Mit friend definierst du da keine Klassenmember. Du definierst damit freie Funktionen. Und wenn mehrmals eine freie Funktion void process() definiert wird, dann ist das natürlich ein Fehler.

    ja klar, aber hier hat eine Überladung ja einen Funktionsparamter und die andere nicht...



  • @th69 sagte in Curious Mixins:

    Hat dich das friend jetzt so verwirrt? Ich mache das aus Gewohnheit, weil Operatoren meistens direkt auf private Member zugreifen.

    Ja, bin kein Freund von Friends...

    Du meinst ja innerhalb eines Templates, dann benutze für die friend-Funktion einen eigenen Template-Parameter:

    template<typename Derived>
    class Comparisons {
     public:
      template <typename T>
      friend bool operator!=(Comparisons<T> const& a, Comparisons<T> const& b);
    };
    
    template <typename Derived>
    bool operator!=(const Comparisons<Derived>& lhs, const Comparisons<Derived>& rhs)
    {
        const Derived& d1 = static_cast<const Derived&>(lhs);
        const Derived& d2 = static_cast<const Derived&>(rhs);
    
        return !(d1 == d2);
    }
    

    da erschliesst sich mir nicht so ganz der Nutzen der Friend Deklaration im class scope.
    Der code funktioniert doch ohne genauso gut.

    Im Grunde geht es mir darum den Barton-Nackman Trick nachzuvollziehen, der ja auf eine friend definition in der CRTP Basisklarre setzt



  • @sewing sagte in Curious Mixins:

    @hustbaer sagte in Curious Mixins:

    @sewing sagte in Curious Mixins:

    1. Wieso wirft mir der Compiler beim Überladen einer Funktion in der CRTP Basisklasse den Fehler
      " redefinition of ‘void process()*"
      friend bool process(const DerivedT &lhs) {
      lhs.bar();
      return true;
      }

    friend void process() {
    }

    Ich überlade doch über ein funktionparameter, das müsste doch erlaubt sein

    Weil du friend nicht verstanden hast.
    Mit friend definierst du da keine Klassenmember. Du definierst damit freie Funktionen. Und wenn mehrmals eine freie Funktion void process() definiert wird, dann ist das natürlich ein Fehler.

    ja klar, aber hier hat eine Überladung ja einen Funktionsparamter und die andere nicht...

    Was meinst du was passiert wenn das Template das die friend Funktionen erzeugt 2x instanziert wird?
    Wie viele void process() gibt es dann im umgebenden Namespace?



  • auch zwei, verstanden. Also liegt das Problem hier nicht bei der Überladung der process function sondern in der Tatsache, dass die eine Variante process() keine parameter hat und damit zweimal die gleiche Version in den surrounding scope injiziert wird?



  • Ja. Wie dir der Compiler ja schon gesagt hat. Und ich versucht habe zu erklären. Ich wundere mich ehrlich gesagt etwas dass das so schwer zu verstehen ist.



  • @sewing sagte in Curious Mixins:

    Im Grunde geht es mir darum den Barton-Nackman Trick nachzuvollziehen, der ja auf eine friend definition in der CRTP Basisklarre setzt

    Der Barton-Nackman Trick ist eigentlich recht einfach.
    Mit friend kannst du innerhalb einer Klassendefinition freie Funktionen definieren. (Die freien Funktionen nur zu deklarieren ginge auch, aber in diesem Fall wollen wir sie auch gleich definieren.)
    Netterweise ist das auch erlaubt wenn die Klassendefinition durch ein Template erzeugt wird. (Die Signatur der freien Funktionen muss dabei natürlich abhängig von (zumindest) einem Template-Argument sein, sonst bekommst du genau das Problem das du hier beobachtet hast.)

    Jetzt ist es bei Mixins normalerweise nötig im Mixin Template per static_cast auf den abgeleiteten Typ zu casten. Weil die Memberfunktionen des Mixin ja Memberfunktionen des Mixins sind, und nicht Memberfunktionen der abgeleiteten Klasse.

    Bei freien Funktionen kannst du dir aber aussuchen welchen Typ die Parameter haben. Und da kann man dann natürlich gleich den Typ der abgeleiteten Klasse verwenden.

    D.h. wenn du

    template <class Derived>
    class Comparable {
    ...
        friend bool operator == (Derived const& x, Derived const& y) {
          ...
        }
    };
    

    schreibst, dann wird beim Instanzieren von Comparable<Foo> auch die freie Funktion bool operator == (Foo const& x, Foo const& y) definiert.

    Diese ist zwar nur per ADL "auffindbar", aber da Foo ja von Comparable<Foo> abgeleitet ist, sucht der Compiler bei ADL für Foo auch bei Comparable<Foo>, und findet daher die per friend definierten Funktionen.

    Und da diese eben direkt Derived als Parameter nehmen, muss man darin nichts mehr casten.

    Steht aber eigentlich alles hier beschrieben: https://en.wikipedia.org/wiki/Barton–Nackman_trick



  • Also deine letzte Erklärung war super verständlich. Hab vielen Dank dafür, hat mir sehr geholfen


Anmelden zum Antworten