2013-01-03 13:41:57
Bindując funkcje do obiektów std::function jesteśmy zmuszeni do podawania tzw. placeholderów. Choć niekiedy ich "mieszanie" może być przydatne, jednak zazwyczaj ustawia się je po kolei (_1, _2, _3, ...). Niestety nie ma prostego sposobu żeby pominąć tą czynność dla tych właśnie przypadków. Rozwiązaniem może być specjalna funkcja szablonowa, która po "wyliczeniu" ilości argumentów obiektu std::function (które są podawane jako argument szablonu) i dopasowaniu do niego odpowiedniej funkcji. Każda z tych funkcji różni się ilością placeholderów w argumentach funkcji std::bind.
template<typename S,typename I,typename FN>struct bind{
template<int N> static typename std::enable_if<N==0>::type get(S ptr,I *ref,FN &fp){fp=std::bind(ptr,ref);}
template<int N> static typename std::enable_if<N==1>::type get(S ptr,I *ref,FN &fp){fp=std::bind(ptr,ref,std::placeholders::_1);}
template<int N> static typename std::enable_if<N==2>::type get(S ptr,I *ref,FN &fp){fp=std::bind(ptr,ref,std::placeholders::_1,std::placeholders::_2);}
...
};
Nie da się niestety napisać różnych wersji tej funkcji automatycznie, ale wątpię że ktokolwiek będzie miał potrzebę używania więcej niż kilku argumentów ;) Jak widać jest tu użyta klasa std::enable_if, (która też jest częścią nowego standardu). Jest to prosta klasa która pozwala wyeliminować funkcje których nie potrzebujemy w zależności od podanego warunku (kompilator na to pozwala, można poczytać o tym więcej tu SFINAE)
Dzięki takiej funkcji (i z pomocą C++11) można łatwo zbudować klasę eventów bardzo przypominającą tę z C# bez kombinowania z castowaniem wskaźników lub budowaniem jej od zera na template-ach. Dla uproszczenia podam prostą implementację klasy "delegate" korzystająca z tej funkcji:
template<typename... P> class delegate{
static const int argsCount=sizeof...(P); // tutaj wyliczamy ilość argumentów szablony przekazanych do klasy
typedef std::function<void(P...)> tdSlot; // typ funkcji, dla uproszczenia zwraca void
tdSlot m_slot;
public:
delegate(){}
~delegate(){}
void connect(const tdSlot &f){
m_slot=f;
}
template<typename MP,typename TP>void connect(MP mp,TP *tp){
bind<MP,TP,tdSlot>::template get<argsCount>(mp,tp,m_slot); // po prostu wywołujemy odpowiednią funkcję w zależności od ilości argumentów
}
void fire(P... args){
m_slot(args...); // odpala delagata z podanymi argumentami (przekazuje je dalej)
}
void operator=(const tdSlot &f){return connect(f);}
};
Mam w tej klasie 2 funkcje connect, pierwsza może służyć do bindowania lambdy/"zwykłej" funkcji lub przyjmowania rezultatu std::bind, druga służy do bindowania metod wewętrznych klas - pierwszy argument to wskaźnik do metody, druga to obiekt na którym wywołamy delegata. Prosty przykład użycia:
class foo{
public:
void zzz(int &x){
std::cout<<"\nfoo::zzz="<<x;
}
void zzzff(float x,float y){
std::cout<<"\nfoo::zzzf="<<x<<", "<<y;
}
};
int main(int argc, char**argv){
foo obiekt;
delegate<float,float> df;
df.connect(&foo::zzzff,&obiekt);
df.fire(5,10);
df.connect(std::bind(&foo::zzzff,&obiekt,std::placeholders::_1,std::placeholders::_2)); // można też tak
df.fire(10,15);
int intArg=66;
delegate<int&> di;
di=[](int &o){std::cout<<"\ninside lambda: "<<o; ++o;};
di.fire(intArg);
di.fire(intArg);
std::cout<<"\nchanged value: "<<intArg;
di.connect(&foo::zzz,&obiekt);
di.fire(intArg);
return 0;
}
Tagi: Programowanie C++ Templates