Curious Mixins



  • Habe eben folgenden Snippet in dem "C+ Templates, the complete guide, 2nd edition" Buch gefunden, was eine Mischform aus crtp und Mixin darstellen soll

    template<template<typename>... Mixins>
    class Point : public Mixins<Point>...
    {
    public:
    double x, y;
    Point() : Mixins<Point>()..., x(0.0), y(0.0) { }
    Point(double x, double y) : Mixins<Point>()..., x(x), y(y) { }
    };
    

    bei mir schmeisst der compiler die Fehler

    error: expected ‘class’ or ‘typename’ before ‘...’ token
    template<template<typename>... Mixins>
    ^~~

    error: type/value mismatch at argument 1 in template parameter list for ‘template<class> class ... Mixins’
    class Point : public Mixins<Point>...

    habe ich etwas übersehen oder falsch verstanden?



  • @sewing sagte in Curious Mixins:

    class Point : public Mixins<Point>...

    Naja du willst hier die Klasse Point von einem Template mit T=Point ableiten.
    Der Compiler kennt aber Point noch gar nicht, weil du ja gerade dabei bist, das zu deklarieren.

    Das macht für mich keinen Sinn. Aber vielleicht gibts ja Anwendungsfälle...
    Aber wenn du das unbedingt machen willst, könnte eine Forward-Deklaration von Point helfen. Aber ob das überhaupt geht, kann ich nicht beurteilen, habe den Fall noch nie gehabt.



  • also was du ansprichst ist hier nicht das problem, sondern einfach das CRTP, ich bin aber der Meinung, dass der Syntax hier nicht so ganz stimmt. Im Buch steht

    Mixins can be made more powerful by combining them with the Curiously
    Recurring Template Pattern (CRTP) described in Section 21.2 on page 495.
    Here, each of the mixins is actually a class template that will be provided with
    the type of the derived class, allowing additional customization to that derived
    class.

    Meiner Meinung nach müsste aber der Snippet lauten

    template<template<typename> class... Mixins>
    class Point : public Mixins<Point<Mixins... >>...
    {
    public:
    double x, y;
    Point() : Mixins<Point>()..., x(0.0), y(0.0) { }
    Point(double x, double y) : Mixins<Point>()..., x(x), y(y) { }
    };```


  • kennt sich denn hier niemand aus mit crtp und dergleichen?

    habe so viele Fragen 😕



  • Die Fehler haben weniger mit Mixin oder CRTP zu tun (s.a. Mixin Classes: The Yang of the CRTP), sondern eher mit der Syntax von Template Parameter Packs.

    Mit welcher C++ Version kompilierst du denn (C++14 oder C++17)?



  • Geht ja eh so wie du schreibst, wo ist das Problem?

    template<template<typename> class... Mixins>
    class Point : public Mixins<Point<Mixins...>>...
    {
    public:
    int x, y;
    Point() : Mixins<Point>()..., x(0), y(0) { }
    Point(int x, int y) : Mixins<Point>()..., x(x), y(y) { }
    };
    
    template <class T>
    struct Fump
    {
        void zap() {
            static_cast<T*>(this)->x = 0;
            static_cast<T*>(this)->y = 0;
        }
    };
    
    int main() {
        volatile int var;
        Point<Fump> pf(21, 21);
        var = pf.x + pf.y;
        pf.zap();
        var = pf.x + pf.y;
        (void)var;
        return 0;
    }
    


  • 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)

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



  • @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?


Anmelden zum Antworten