[template] functor



  • Wie schon gesagt, das const im Template parameter ist wichtig, denn sonst ist fct ein pointer auf einen nonconst member function und kann fuer const "K"s nicht aufgerufen werden. Bei pointer to membern muss immer noch ein zusaetzliches Argument uebergeben werden, naemlich wessen Member das nun sein soll. Das wird eben ueber den Operator .* bzw. ->* erledigt. Das Konstrukt

    (r.*fct)
    

    bedeutet soviel wie "Uebergebe r als aktuelles Objekt an fct." Da fct selbst zwar ein konstanter pointer ist, aber auf ein nicht konstantes Objekt verweist, versucht der Compiler, aus dem konstanten r ein nichtkonstantes zu machen, damit fct angewendet werden kann. Zu recht beschwert er sich dann.
    Um beides machen zu koennen, muesste das Ganze dann so aussehen:

    template<class K, typename M, M (K::*fct)()> // Allgemeiner Fall
    struct compareMethods
    {
            bool operator()(K& r, K& l) const // da fct nicht const ist, kann auch K nicht const sein. das const hinter der deklaration besagt dass compareMethods const sein kann
            { return (r.*fct)() < (l.*fct)();  }
    };
    
    template<class K, typename M>
    struct compareMethods<K, M, M (K::*fct)() const> //partielle Spezialisierung fuer const pointers
    {
      bool operator()(const K& r, const K& l) const //hier koennen die K's auch const sein, muessen sie aber nicht (const hinzufuegen geht ja problemlos)
      { return (r.*fct)() < (l.*fct)();  }
    };
    

    Damit kannst du sowohl Funktionspointer auf konstante als auch auf nichtkonstante Memberfunktionen benutzen. bei letzteren kannst du allerdings keine konstanten Parameter uebergeben (klar, man kann keine nichtkonstanten Funktionen fuer konstante Objekte aufrufen)

    }



  • @pumuckl:
    genau sowas hatte ich auch versucht. aber leider gibt es imme räger bei der spezielisierung des funktionpointers.

    class Klasse 
    { 
    public: 
       Klasse(double val1, double val2 ) : val1(val1),val2(val2) {} 
       double getVal1()       { return val1; } 
       double getVal2() const { return val2; } // <- hier const
    
    private: 
       double val1, val2; 
    }; 
    
    template<class K, typename M, M (K::*fct)()> // Allgemeiner Fall
    struct compareMethods
    {
       bool operator()(K& r, K& l) const // da fct nicht const ist, kann auch K nicht const sein. das const hinter der deklaration besagt dass compareMethods const sein kann
       { return (r.*fct)() < (l.*fct)();  }
    };
    
    template<class K, typename M>
    /*Z43*/struct compareMethods<K, M, M (K::*fct)() const > //partielle Spezialisierung fuer const pointers
    {
       bool operator()(const K& r, const K& l) const //hier koennen die K's auch const sein, muessen sie aber nicht (const hinzufuegen geht ja problemlos)
       { return (r.*fct)() < (l.*fct)();  }
    };
    
    int main(int argc, char** argv)
    {
       std::list<Klasse> l;
       l.push_back(Klasse(10,10));
       l.push_back(Klasse(1,5));
       l.push_back(Klasse(50,3));
       l.push_back(Klasse(4,6));
       l.push_back(Klasse(0,8));
       l.sort( compareMethods<Klasse, double,  &Klasse::getVal1 >() ); 
       l.sort( compareMethods<Klasse, double,  &Klasse::getVal2 >() ); 
    }
    

    führt zu:

    main.cpp(43) : error C2143: syntax error : missing ')' before '<tag>::*'
    main.cpp(44) : error C2143: syntax error : missing ';' before '{'
    main.cpp(46) : error C2143: syntax error : missing ';' before '{'
    main.cpp(47) : error C2143: syntax error : missing ';' before '}'
    main.cpp(49) : error C2146: syntax error : missing ',' before identifier 'argv'
    main.cpp(49) : error C2065: 'argv' : undeclared identifier
    main.cpp(49) : error C2143: syntax error : missing ',' before ')'
    main.cpp(50) : error C2143: syntax error : missing ';' before '{'



  • Hm, ich seh grade, das ist ja garkeine Spezialisierung, sondern ein voellig anderer Templateparameter. so aehlich wie template<bool> keine Spezialisierung von template<int> ist. Die beiden Zeiger sind einfach verschiedene Typen. Spezialisiern kann man nur, wenn man einen Typ-parameter weiter einschraenkt oder einen Konstantenparameter wie wir ihn hier haben, explizit angibt.

    Wenn ich mich recht erinnere geht die "Ueberladung" von Klassentemplates auch nicht, sprich sowas wie

    template<class K, typename M, M (K::*fct)()> // Nonconst Fall
    struct compareMethods;
    template<class K, typename M, M (K::*fct)() const> // Const Fall
    struct compareMethods;
    

    klappt nicht, weil zwei unterschiedliche Klassentemplates nicht den gleichen Namen haben duerfen.

    Bleiben also auch wieder nur zwei verschiedene Klassentemplates compareMethods und compareConstMethods, sowie zwei ueberladene Funktionentemplates, die mit argument deduction das eine oder andere von beiden zurueck geben.



  • Gesammelte Werke aus diesem thread dann wie folgt. Man kann als zusaetzlichen Templateparameter auch noch die Art des Vergleichs bestimmen. Wird ja nicht immer operator< sein:

    template <class C, typename M, class Comparator>  //for member variables
    sruct compareMem {
      typedef const M C::*MemPtr;
      compare(MemPtr ptr, Comparator c) : mp_(ptr), comp(c);
      bool operator()(const C& l, const C& r) {return comp(l.*mp_,m.*mp_);}
    private:
      MemPtr mp_;
      Comparator comp;
    };
    
    template<class C, typename E, class Comparator> //for non const methods
    struct compareMethod {
      typedef M C::(*MemFktPtr)();
      compare(MemFktPtr ptr, Comparator c) : mfp_(ptr), comp(c);
      bool operator()(C& l, C& r) {return comp((l.*mfp_)(), (m.*mfp_)());}
    private:
      MemPtr mfp_;
      Comparator comp;
    };
    
    template<class C, typename E, class Comparator> //for const methods
    struct compareConstMethod {
      typedef M C::(*ConstMemFktPtr)() const;
      compare(ConstMemFktPtr ptr, Comparator c) : cmfp_(ptr), comp(c);
      bool operator()(const C& l, const C& r) {return (comp(l.*cmfp_)(), (m.*cmfp_)());}
    private:
      MemPtr cmfp_;
      Comparator comp;
    };
    
    //overloads for custom comparator
    template <class C, typename M, class Comparator> //template argument deduction
    compareMember<C,M,Comparator> make_compare(M C::*ptr, Comparator c) {compareMember<C,M> cmp(ptr,c); return cmp}
    
    template <class C, typename M, class Comparator> //template argument deduction again
    compareMethod<C,M,Comparator> make_compare(M C::(*ptr)(), Comparator c) {compareMethod<C,M> fcmp(ptr,c); return fcmp}	
    
    template <class C, typename M, class Comparator> //template argument deduction again
    compareConstMethod<C,M,Comparator> make_compare(M C::(*ptr)() const, Comparator c) {compareConstMethod<C,M,Comparator> fcmp(ptr,c); return fcmp}	
    
    //overloads for default comparator std::less<M>
    template <class C, typename M> //template argument deduction
    compareMember<C,M,Comparator> make_compare(M C::*ptr) {return make_compare(ptr, std::less<M>()}
    // etc.
    

    Allmaehlich reichts fuer n eigenen Header "comparemembers.h" 🙂



  • *lach* genauso isses. was lange währt, wird endlich gut 😉

    was mir an der letzteren nicht gefällt ist, dass jedes mal eine member variable angelegt werden muss.
    und kannst du mir in kurzen worten dieses deduction erklären? der begriff fiel hier schon öfter, was genau ist das problem für den compiler?



  • Argument Deduction bedeutet, daß der Compiler selber herausfinden kann, was er für die Template-Parameter einsetzen muss. Das heißt, wenn du irgendwo 'make_compare(&klasse::memb)' stehen hast, dann findet er erstens heraus, auf welche der drei Überladungen er zugreifen soll (Member, const-Getter oder normaler Getter) und kann zweitens auch selber herausfinden, mit welchen Template-Parametern er sie verwenden soll.
    (das erspart dir als Programmierer, die Typen selber raussuchen zu müssen)



  • Das ist eher kein Problem fuer den Compiler, sondern eine Ersparnis fuer den Programmierer (und viel mehr).

    Nehmen wir an, du hast ein Funktionstemplate

    template<typename T>
    void f(T param){}
    

    Dann muesstest du beim Aufruf eigentlich immer die Templateparameter angeben, z.B.

    int i =1;
    double d = 2.0;
    f<int>(i);
    f<double>(d);
    

    Template argument deduction bedeutet, dass der Compiler sich selbst ueberlegen kann, fuer welchen Typ er das Funktionstemplate initialisieren muss, schliesslich weiss er ja, von welchem Typ der Funktionsparameter ist. Dann muss der Templateparameter nicht mehr angegeben werden:

    f(i); //i ist ein int, also f<int>
    f(d); //d ist double, also f<double>
    

    std::sort selbst macht Gebrauch davon, denn sowohl der Iteratortyp als auch der Comparatortyp sind Templateparameter, die allerdings nicht angegeben werden muessen, weil sie ja als Funktionsparameter uebergeben werden.

    In meinem Beispiel oben kann der Compiler aus einem einzigen Funktionsparameter beide Templateparameter herleiten, da in dem Pointer ja sowohl der Typ der Klasse als auch der Typ der Membervariable drinsteckt.



  • CStoll schrieb:

    (das erspart dir als Programmierer, die Typen selber raussuchen zu müssen)

    ...was sehr hilfreich sein kann (oder weiss jemand auswendig, was für einen Rückgabetyp std::tr1::bind(&Klasse::member, this) hat?)



  • Wenn man so einen Rueckgabetyp irgendeiner Variablen zuweisen moechte, muss man das natuerlich wissen. Wenn man das Ding dann aber nur Beispielsweise an sort weitergibt, ist das voellig wurscht, denn sort selbst ist der Typ voellig egal, hauptsache der operator() ist definiert.



  • alter falter. hört sich sehr komfortabel an... werde mich die tage mal näher damit beschäftigen. gibt es irgendwelche sinnigen links zu dem thema?



  • pumuckl schrieb:

    Wenn man so einen Rueckgabetyp irgendeiner Variablen zuweisen moechte, muss man das natuerlich wissen. Wenn man das Ding dann aber nur Beispielsweise an sort weitergibt, ist das voellig wurscht, denn sort selbst ist der Typ voellig egal, hauptsache der operator() ist definiert.

    Eben dank der Deduktion. Ohne Deduktion müsste man sort einen Templateparameter mitgeben, nämlich o.g. Rückgabetyp (genaugenommen zwei, nämlich auch den Typ des Iterator).



  • http://www.josuttis.de/tmplbook/index.html
    in dem dort beschriebene Buch steht unheimlich viel drin zum Thema Templates, angefangen bei Syntax, saemtlichen Spezialfaellen, ueber das, was der Standard dazu hergibt bis hin zu teils recht ueberraschenden Dingen, die man mit templates anstellen kann.



  • und das buch liegt direkt neben mir... hmm... sollte ich doch nochmal reinschauen 🙂

    @pumuckl:
    kannst du bitte noch für "deinen header" den ensprechenden list.sort(..) befehl angeben? evtl gleich in dem entsprechenden thread



  • l.sort(make_compare(&Klasse::getVal1));
    

    oder

    l.sort(make_compare(&Klasse::getVal1, std::less<double>));
    

    Der Compiler sieht, ob es sich bei getVal1 um eine Variable, Funktion oder const Funktion handelt und benutztdie enstsprechende Überladung von make_compare, außerdem erkennt er auch noch die Templateparameter aus dem Argument, in diesem Fall Klasse und double.
    Du könntest auch jede andere Membervariable bzw. -funktion auf exakt die selbe Art angeben, hauptsache sie ist public 🙂
    Ich werd nachher das alles mal ins Reine schreiben und noch eine Version hinzufügen, wo bei der zweiten Version die Angabe von double für std::less nicht mehr nötig ist (Stichwort template template Parameter)



  • na, da freu ich mich ja schon drauf...



  • sodele. Work's done.
    Die Loesung mit zwei verschiedenen Klassen MemFctCompare und ConstMemFctCompare hat nicht geklappt. ALlerdings ist der Compiler so freundlich, wenn man den Funktionen einen pointer auf eine const Funktion uebergibt, das template mit der klasse als const C zu initialisieren. Fehlte nurnoch eine Spezialisierung fuer const C, die den pointer auf eine const funktion statt einer normalen Funktion speichert und den operator() fuer zwei const referenzen implementiert. (sonst haette der compiler wie gehabt wegen der non-constness der funktion gemosert)

    Die komlette Definition der ganzen Geschichte:

    // file: memcompare.h
    // definition of comparators (comparison functors)
    // for member variables and member function results
    /////////////////////////////////////////////////
    
    #ifndef MEMCOMPARE_H
    #define MEMCOMPARE_H
    
    #include <functional>
    
    //Comparator for member variables
    template <class C, typename M, class Comparator = std::less<M> >  
    struct MemComparator 
    {
      typedef M C::*MemPtr;
    
      MemComparator(MemPtr ptr, Comparator c = Comparator()) 
        : mp_(ptr), comp(c)
        {};
    
      bool operator()(const C& lhs, const C& rhs) 
      { return comp(lhs.*mp_, rhs.*mp_); }
    
    private:
      MemPtr mp_;
      Comparator comp;
    };
    
    //Comparator for non const member functions
    template<class C, typename M, class Comparator = std::less<M> > //for non const methods
    struct MemFctComparator 
    {
      typedef M (C::*MemFctPtr)();
    
      MemFctComparator(MemFctPtr ptr, Comparator c = Comparator()) 
        : mfp_(ptr), comp(c)
        {};
    
      bool operator()(C& lhs, C& rhs) 
      { return comp((lhs.*mfp_)(), (rhs.*mfp_)()); }
    
    private:
      MemFctPtr mfp_;
      Comparator comp;
    };
    
    //specialization for const member functions
    template<class C, typename M, class Comparator> 
    struct MemFctComparator<const C, M, Comparator>
    {
      typedef M (C::*ConstMemFctPtr)() const;
    
      MemFctComparator(ConstMemFctPtr ptr, Comparator c = Comparator()) 
        : cmfp_(ptr), comp(c)
        {};
    
      bool operator()(const C& lhs, const C& rhs) 
      { return comp((lhs.*cmfp_)(), (rhs.*cmfp_)()); }
    
    private:
      ConstMemFctPtr cmfp_;
      Comparator comp;
    };
    
    //////////////////////////////////////////////////////////////////////
    //Factory functions for member comparators
    //////////////////////////////////////////////////////////////////////
    
    template <class Comparator, class C, typename M> 
    MemComparator<C,M,Comparator> 
      membercomp(M C::*ptr, Comparator c = Comparator()) //overload (1)
    { 
      MemComparator<C,M,Comparator> cmp(ptr,c); 
      return cmp;
    }
    
    template <class Comparator, class C, typename M> 
    MemFctComparator<C,M,Comparator> 
      membercomp(M (C::*ptr)(), Comparator c = Comparator()) //overload (2)
    {
      MemFctComparator<C,M,Comparator> cmp(ptr,c); 
      return cmp;
    }   
    
    //////////////////////////////////////////////////////////////////////
    //Factory functions for member comparators with comparator templates
    //////////////////////////////////////////////////////////////////////
    
    template <template<typename> class ComparatorTpl, class C, typename M>
    MemComparator<C,M,ComparatorTpl<M> > 
      membercomp(M C::*ptr, ComparatorTpl<M> c = ComparatorTpl<M>()) //overload (3)
    { 
      MemComparator<C,M,ComparatorTpl<M> > cmp(ptr,c); 
      return cmp;
    };
    
    template <template<typename> class ComparatorTpl, class C, typename M>
    MemFctComparator<C,M,ComparatorTpl<M> > 
      membercomp(M (C::*ptr)(), ComparatorTpl<M> c = ComparatorTpl<M>()) //overload (4)
    {
      MemFctComparator<C,M,ComparatorTpl<M> > cmp(ptr,c); 
      return cmp;
    }   
    
    //////////////////////////////////////////////////////////////////////
    //Factory functions for member comparators with default comparator std::less
    //////////////////////////////////////////////////////////////////////
    
    template <class C, typename M>
    MemComparator<C,M> membercomp(M C::*ptr) //overload (5)
    { 
      MemComparator<C,M> cmp(ptr); 
      return cmp;
    };
    
    template <class C, typename M>
    MemFctComparator<C,M> membercomp(M (C::*ptr)()) //overload (6)
    { 
      MemFctComparator<C,M> cmp(ptr); 
      return cmp;
    };
    
    #endif
    

    und dazu das Testprogramm das ich mir am ende geschrieben hab (beinhaltet alle Moeglichkeiten, memcompare() aufzurufen:

    #include "memcompare.h"
    #include <list>
    using namespace std;
    
    struct S {
      S(int i) :x(i) {};
      int x;
      float f() {return x;};
      double g() const {return x;};
    };
    
    struct intComp {
      bool operator()(int l, int r)
        { return l > r; }
    };
    
    struct dblComp {
      bool operator()(double l,  double r)
        { return l > r; }
    };
    
    template <typename T>
    struct genComp {
      bool operator()(const T& l, const T& r)
        { return l > r; }
    };
    
    int main()
    {
      S a(1);
      S b(2);
      list<S> sList;
      sList.push_back(a);
      sList.push_back(b);
      sList.sort(membercomp(&S::x,intComp()));  //calls overload (1)
      sList.sort(membercomp<intComp>(&S::x));   //same
      sList.sort(membercomp(&S::x));            //calls overload (5)
      sList.sort(membercomp<genComp>(&S::x));   //calls overload(3)
      sList.sort(membercomp(&S::x, genComp<int>())); //calls overload(1)
      //same for nonconst function
      sList.sort(membercomp(&S::f, dblComp())); //overload(2)
      sList.sort(membercomp<dblComp>(&S::f));   //same	 
      sList.sort(membercomp(&S::f));            //overload(6)
      sList.sort(membercomp<genComp>(&S::f));   //overload(4)
      //same for const function
      sList.sort(membercomp(&S::g, dblComp())); //overload(2)
      sList.sort(membercomp<dblComp>(&S::g));   //same	 
      sList.sort(membercomp(&S::g));	    //overload(6)
      sList.sort(membercomp<genComp>(&S::g));   //overload(4)
    }
    


  • Naechster Schritt waere ein Comparator, der auf irgendeine Weise aus einem Objekt einen Wert errechnet (also nicht unbedingt durch Memberfunktionen oder -variablen) und diesen dann durch den gewuenschten Comparator vergleicht.

    Bsp:

    Struct S{
    int x;
    int y;
    };
    
    int product(const S& s)
    { return s.x*s.y; }
    
    struct Sum {
      int operator()(const S& s)
      { return s.x+s.y; }
    }
    
    int main() {
      std::list<S> sList;
      //...
      sList.sort(compareby(Sum()));
      sList.sort(compareby<std::greater>(&product));
      sList.sort(compareby(&S::x)); //hatten wir ja schon
    }
    


  • astreine arbeit... wird ab sofort verwendet. Danke sagt: SirAnn



  • ups... da war ich wohl etwas voreilig.
    Unter VS2005 kann man das Bsp. nicht compilieren... es kommen SEHR viele Fehlermedlungen 🙂

    EDIT: Zeilennummern müssten übereinstimmen...

    c:\users\freud\documents\visual studio 2005\projects\test\test\main.cpp(45) : error C2668: 'membercomp' : ambiguous call to overloaded function
    1> c:\users\freud\documents\visual studio 2005\projects\test\test\memcompare.h(102): could be 'MemFctComparator<C,M,Comparator> membercomp<genComp,S,float>(M (__thiscall S::* )(void),genComp<T>)'
    1> with
    1> [
    1> C=S,
    1> M=float,
    1> Comparator=genComp<float>,
    1> T=float
    1> ]
    1> c:\users\freud\documents\visual studio 2005\projects\test\test\memcompare.h(94): or 'MemComparator<C,M,Comparator> membercomp<genComp,S,float(void)>(M (__thiscall S::* ),genComp<T>)'
    1> with
    1> [
    1> C=S,
    1> M=float (void),
    1> Comparator=genComp<float (void)>,
    1> T=float (void)
    1> ]
    1> while trying to match the argument list '(float (__thiscall S::* )(void))'
    1>c:\users\freud\documents\visual studio 2005\projects\test\test\main.cpp(45) : error C2660: 'std::list<_Ty>::sort' : function does not take 1 arguments
    1> with
    1> [
    1> _Ty=S
    1> ]
    1>c:\users\freud\documents\visual studio 2005\projects\test\test\main.cpp(47) : error C2664: 'membercomp' : cannot convert parameter 1 from 'double (__thiscall S::* )(void) const' to 'double (__thiscall S::* )(void)'
    1> Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast
    1>c:\users\freud\documents\visual studio 2005\projects\test\test\main.cpp(47) : error C2660: 'std::list<_Ty>::sort' : function does not take 1 arguments
    1> with
    1> [
    1> _Ty=S
    1> ]
    1>c:\users\freud\documents\visual studio 2005\projects\test\test\main.cpp(48) : error C2665: 'membercomp' : none of the 3 overloads could convert all the argument types
    1> c:\users\freud\documents\visual studio 2005\projects\test\test\memcompare.h(74): could be 'MemComparator<C,M,Comparator> membercomp<dblComp,S,double(void)>(M (__thiscall S::* ),Comparator)'
    1> with
    1> [
    1> C=S,
    1> M=double (void),
    1> Comparator=dblComp
    1> ]
    1> c:\users\freud\documents\visual studio 2005\projects\test\test\memcompare.h(113): or 'MemComparator<C,M,Comparator> membercomp<dblComp,double(void)>(M (__thiscall dblComp::* ))'
    1> with
    1> [
    1> C=dblComp,
    1> M=double (void),
    1> Comparator=std::less<double (void)>
    1> ]
    1> c:\users\freud\documents\visual studio 2005\projects\test\test\memcompare.h(120): or 'MemFctComparator<C,M,Comparator> membercomp<dblComp,double>(M (__thiscall dblComp::* )(void))'
    1> with
    1> [
    1> C=dblComp,
    1> M=double,
    1> Comparator=std::less<double>
    1> ]
    1> while trying to match the argument list '(double (__thiscall S::* )(void) const)'
    1>c:\users\freud\documents\visual studio 2005\projects\test\test\main.cpp(48) : error C2660: 'std::list<_Ty>::sort' : function does not take 1 arguments
    1> with
    1> [
    1> _Ty=S
    1> ]
    1>c:\users\freud\documents\visual studio 2005\projects\test\test\main.cpp(49) : error C2664: 'membercomp' : cannot convert parameter 1 from 'double (__thiscall S::* )(void) const' to 'double (__thiscall S::* )(void)'
    1> Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast
    1>c:\users\freud\documents\visual studio 2005\projects\test\test\main.cpp(49) : error C2660: 'std::list<_Ty>::sort' : function does not take 1 arguments
    1> with
    1> [
    1> _Ty=S
    1> ]
    1>c:\users\freud\documents\visual studio 2005\projects\test\test\main.cpp(50) : error C2664: 'membercomp' : cannot convert parameter 1 from 'double (__thiscall S::* )(void) const' to 'double (__thiscall S::* )(void)'
    1> Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast
    1>c:\users\freud\documents\visual studio 2005\projects\test\test\main.cpp(50) : error C2660: 'std::list<_Ty>::sort' : function does not take 1 arguments
    1> with
    1> [
    1> _Ty=S
    1> ]

    ne idee?



  • Auweia. Das sieht mir doch arg nach dem Bug (oder dem Feature) aus, das im thread T C::* zu habgierig? gepostet wurde: VS scheint keinen Unterschied zwischen einen Pointer auf float und einem Pointer auf eine Funktion, die float zurueckgibt zu machen. Standardkonform ist das zumindest nicht. Kein Plan, was man da machen kann 😞


Anmelden zum Antworten