Ładowanie
popup

C++11: Jak się pozbyć placeholderów?

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