[template] functor
-
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; }; #endifund 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

-
pumuckl schrieb:
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.
Das ist dann allerdings kein Standard C++ (Edit: wohlgemerkt, ich beziehe mich auf const-Memberfunktionen, das hat nichts mit dem Problem im anderen Thread zu tun). Weder Visual C++ noch Comeau C++ kommen mit deinem Code zurecht.
Etwas weniger kompliziert geht es mit Spezialisierungen:
#ifndef MEMCOMPARE_H #define MEMCOMPARE_H #include <functional> // Hilfsfunktion für std::less, kriegen wir auch über type_traits hin template <typename T> struct Pointee; template <typename T, typename C> struct Pointee<T C::*> { typedef T type; }; template <typename T, typename C> struct Pointee<T (C::*)()> { typedef T type; }; template <typename T, typename C> struct Pointee<T (C::*)() const> { typedef T type; }; // Primärtemplate bleibt undefiniert template <typename Ptr, typename Comp = std::less<typename Pointee<Ptr>::type> > class MemComp; // partielle Spezialisierung für Datamember template <typename C, typename M, typename Comp> class MemComp<M C::*, Comp > : private Comp // nach Möglichkeit EBO nutzen { typedef M C::*MemPtr; public: MemComp(MemPtr ptr, Comp c = Comp()) : Comp(c), mp_(ptr) {} bool operator()(const C& lhs, const C& rhs) const { return Comp::operator()( lhs.*mp_, rhs.*mp_ ); } private: MemPtr mp_; }; // für non-const Memberfunktionen - die werden sonst von obiger Spezialisierung gefutter template <typename C, typename M, typename Comp> class MemComp<M (C::*)(), Comp> : private Comp { typedef M (C::*MemFctPtr)(); public: MemComp(MemFctPtr ptr, Comp c = Comp()) : Comp(c), mfp_(ptr) {} bool operator()(C& lhs, C& rhs) const { return Comp::operator()( (lhs.*mfp_)(), (rhs.*mfp_)() ); } private: MemFctPtr mfp_; }; // für const Memberfunktionen template <typename C, typename M, typename Comp> class MemComp<M (C::*)() const, Comp> : private Comp { typedef M (C::*MemFctPtr)() const; public: MemComp(MemFctPtr ptr, Comp c = Comp()) : Comp(c), mfp_(ptr) {} bool operator()(const C& lhs, const C& rhs) const { return Comp::operator()( (lhs.*mfp_)(), (rhs.*mfp_)() ); } private: MemFctPtr mfp_; }; // Factoryfunktionen template <typename Ptr> MemComp<Ptr> membercomp(Ptr p) { return MemComp<Ptr>(p); } template<typename Comp, typename Ptr> MemComp<Ptr, Comp> membercomp(Ptr p, Comp c = Comp()) { return MemComp<Ptr, Comp>(p, c); } template<template<typename> class Comp, typename Ptr> MemComp<Ptr, Comp<typename Pointee<Ptr>::type> > membercomp(Ptr p, Comp<typename Pointee<Ptr>::type> c = Comp<typename Pointee<Ptr>::type>()) { return MemComp<Ptr, Comp<typename Pointee<Ptr>::type> >(p, c); } #endifEdit2: Dass das mit const C systemwidrig wäre, erkennt man auch so:
ich benutze wieder ein typedef um eine Memberfunktion zu deklarierentypedef void foo(); struct X { foo bar; // ok const foo bar; // nicht ok, will void X::bar() const deklarieren, Funktionstypen (foo) sind aber niemals cv-qualifiziert };mit
struct Y { void foo() const; };um einen Memberfunktionspointer zu bilden, schreiben wir &Y::foo - &const Y::foo wäre schlicht fehlerhaft.
-
oje, da habe ich ja was losgetreten...
der header von camper funktioniert mit punmuckls main, wenn man hinter die überladenen ()-operator-Methoden ein const setzt...
wieso das eine geht und das andere nicht, da bin ich mittlerweile etwas ratlos
-
Mit einer kleinen Einschraenkung compiliert das auch bei mir:
wenn du MemComp::operator() als const deklarierst, verlangst du implizit auch constness des geerbten Comp::operator(). Um das zu umgehen, muessten eine const und eine nonconst-version deklariert werden. Wenn dann trotzdem von einem Comp abgeleitet wird, der nur den non-const operator() liefert, denn wenn der Comp::operator() const nicht aufgerufen wird, wird er auch nicht initialisiert (da Funktionstemplate) und der Compiler hat nix zu meckern./edit: Beispiel fuer muff:
template <typename T> struct mycomp{ mycomp() counter_(0) {}; bool operator()(const T& a, const T&b) //NICHT const { counter_++; //und zwar DESHALB return a<b; } private: unsigned int counter_; };MemComp</.../,mycomp> definiert operator() als const, ruft aber eine funktion auf, die deifinitiv nicht const ist, und zwar von seiner Basisklasse. Und das gefaellt dem Compiler selbstverstaendlich nicht.
-
pumuckl schrieb:
Mit einer kleinen Einschraenkung compiliert das auch bei mir:
wenn du MemComp::operator() als const deklarierst, verlangst du implizit auch constness des geerbten Comp::operator(). Um das zu umgehen, muessten eine const und eine nonconst-version deklariert werden. Wenn dann trotzdem von einem Comp abgeleitet wird, der nur den non-const operator() liefert, denn wenn der Comp::operator() const nicht aufgerufen wird, wird er auch nicht initialisiert (da Funktionstemplate) und der Compiler hat nix zu meckern.Dann so
template <typename C, typename M, typename Comp> class MemComp<M (C::*)(), Comp> { typedef M (C::*MemFctPtr)(); public: MemComp(MemFctPtr ptr, Comp c = Comp()) : cp_(c), mfp_(ptr) {} bool operator()(C& lhs, C& rhs) const { return cp_( (lhs.*mfp_)(), (rhs.*mfp_)() ); } private: mutable Comp cp_; MemFctPtr mfp_; };sieht aber sehr unnatürlich aus. Das Ergebnis eines Vergleichs ist normalerweise nur von den zu vergleichenden Objekten abhängig - eine entsprechende Funktion daher üblicherweise stateless.
Edit: dein Beispiel sollte den Zähler mutable haben - das hat nichts mit der logischen Konstanz des Operators zu tun.
Edit2: naja, man kann sicher Fälle finden, wo der Operator nicht konstant sein sollte... bloß der Regelfall dürfte es nicht sein.
Edit3: in unserem Falle ist der verwendete Comparator privat - also entweder verändert er höchtens globale Daten, dann hat das nichts mit const des Operators zu tun, oder die innere Zustandsänderung beeinflußt den Vergleich selbst - dann ist es keine richtige Vergleichsfunktion mehr. Jede andere Zustandsänderung des Objekts wäre ohnehin nicht beobachtbar und daher sinnlos.
-
Naja ich wuerd wie gesagt einfach eine const udn eine nonconst version ueberladen, mit dem Erben wegen EBCO hast du ja durchaus recht. Klar ist das nicht ueblich, dass ein Comparator einen inneren State hat, aber man weiss ja nie was manchen Leuten so durch den Kopf spukt
(Beispielsweise der Zehler, den man nutzen koennte um die Effizienz verschiedener Sortieralgorithmen zu messen)
-
neuer Entwurf
#ifndef MEMCOMPARE_H #define MEMCOMPARE_H #include <functional> // Hilfsblob template <typename T> struct MemberInfo; template <typename T, typename C> struct MemberInfo<T C::*> { typedef T type; typedef C class_type; static T& apply(C& c, T C::* ptr) { return c.*ptr; } static const T& apply(const C& c, T C::* ptr) { return c.*ptr; } }; template <typename T, typename C> struct MemberInfo<T (C::*)()> { typedef T type; typedef C class_type; static T apply(C& c, T (C::*ptr)()) { return (c.*ptr)(); } }; template <typename T, typename C> struct MemberInfo<T (C::*)() const> { typedef T type; typedef C class_type; static T apply(const C& c, T (C::*ptr)() const) { return (c.*ptr)(); } }; template <typename Ptr, typename Comp = std::less<typename MemberInfo<Ptr>::type> > class MemComp : private Comp // nach Möglichkeit EBO nutzen { typedef typename MemberInfo<Ptr>::class_type C; public: MemComp(Ptr ptr, Comp c = Comp()) : Comp(c), mp_(ptr) {} bool operator()(C& lhs, C& rhs) { return Comp::operator()( MemberInfo<Ptr>::apply(lhs, mp_), MemberInfo<Ptr>::apply(rhs, mp_) ); } bool operator()(C& lhs, C& rhs) const { return Comp::operator()( MemberInfo<Ptr>::apply(lhs, mp_), MemberInfo<Ptr>::apply(rhs, mp_) ); } bool operator()(const C& lhs, const C& rhs) { return Comp::operator()( MemberInfo<Ptr>::apply(lhs, mp_), MemberInfo<Ptr>::apply(rhs, mp_) ); } bool operator()(const C& lhs, const C& rhs) const { return Comp::operator()( MemberInfo<Ptr>::apply(lhs, mp_), MemberInfo<Ptr>::apply(rhs, mp_) ); } private: Ptr mp_; }; // Factoryfunktionen template <typename Ptr> MemComp<Ptr> membercomp(Ptr p) { return MemComp<Ptr>(p); } template<typename Comp, typename Ptr> MemComp<Ptr, Comp> membercomp(Ptr p, Comp c = Comp()) { return MemComp<Ptr, Comp>(p, c); } template<template<typename> class Comp, typename Ptr> MemComp<Ptr, Comp<typename MemberInfo<Ptr>::type> > membercomp(Ptr p, Comp<typename MemberInfo<Ptr>::type> c = Comp<typename MemberInfo<Ptr>::type>()) { return MemComp<Ptr, Comp<typename MemberInfo<Ptr>::type> >(p, c); } #endif