Zaawansowane CPP/Ćwiczenia 11: Funktory

From Studia Informatyczne

Ćwiczenie 1

Zaimplementuj adapter compose_f_gx_hy realizujący złożenie dwuargumentowe \displaystyle f(g(x),h(y)).

Rozwiązanie

Zobacz plik compose_f_gx_hy.h.

Ćwiczenie 2

Korzystając z klasy functor_traits zaimplementuj adpter bind1st, który bedzie działał zarówno dla funktorów jedno-, jak i dwuargumentowych.

Rozwiązanie

W klasie binder1st umieszczamy dwa operatory nawiasów:

template<typename F> class binder1st :
public  bind1st_f_type<typename functor_traits<F>::f_type>::f_type {
  typedef  typename functor_traits<F>::arg1_type  bind_type;
const bind_type _val; F _op; public: binder1st(F op,bind_type val):_op(op), _val(val) {};
typename F::result_type operator()(typename functor_traits<F>::arg2_type x) { return _op(_val,x); } typename F::result_type operator()() { return _op(_val); } };

Dzięki konkretyzacji na żądanie wygenerowany zostanie tylko ten, którego użyjemy w kodzie. Szablon bind1st_f_type służy do określenia typu funktora po związaniu pierwszego argumentu. Do jego implemenatcji wykorzystujemy specjalizacje częściowe:

template<typename F> struct bind1st_f_type;
template<typename A1,typename A2,typename R> struct bind1st_f_type<std::binary_function<A1,A2,R> > { typedef std::unary_function<A2,R> f_type; };
template<typename A1,typename R> struct bind1st_f_type<std::unary_function<A1,R> > { typedef generator<R> f_type; };

Całość kodu znajduje się w pliku bind.h.

Ćwiczenie 3

Zaimplementuj funktor implementujący, składanie funkcji poprzez wykonywanie ich po kolei np.:

macro(f1,f2)(x)

powinno wykonać

f1(x);f2(x);

Wartości zwracane przez te funkcje są ignorowane. Funkcja macro powinna zwracać funktor odpowiedniego typu (posiadający odpowiednie typy stowarzyszone) tak, aby możliwe było dalsze składanie np.:

macro(macro(f1,f2),f3)(x)

powinno wywołać:

f1(x);f2(x);f3(x);

Rozwiązanie

Zobacz plik macro.h.

Ćwiczenie 4

Zmodyfikuj powyższy szablon tak aby można było mieszać funkcje o różnej liczbie agrgumentów np.:

int f();
void g(double);
void h(double,int);
macro(f,g)(x);

powinno wywołać

f();g(x)}

a

macro(g,h)(3.14,0);

powinno wywołać

g(3.14);h(3.14,0)

Podpowiedź

Można zacząć od zaimplementowania pomocniczego adaptera call, który wywołuje funkcje dopasowując liczbę argumentów:

int f();
void g(double);
void h(double,int);
call<double>(f)(x); /* woła f();*/ call<double>(g)(x); /* woła g();*/ call<double,double>(f)(x,y); /* woła f();*/ call<double,int>(g)(x,i); /* woła g(x);*/ call<double,int>(h)(x,i); /* woła h(x,i);*/

Argumenty szablonu określają typy argumentów funktora zwracanego przez call<>().

Rozwiązanie

Zaczynamy od implemenatcji adaptera call. W tym celu definiujemy szablon:

template<typename F,typename A1,typename A2> struct call_t2: public 
std::binary_function<A1,
                     A2,
                     typename functor_traits<F>::result_type> {
 typedef typename functor_traits<F>::result_type result_type;
 typedef typename functor_traits<F>::arg1_type arg1_type;
 typedef typename functor_traits<F>::arg2_type arg2_type;
F _f;
public: call_t2(F f):_f(f) {};

który wyposażamy w trzy funkcje:

 result_type call(A1 a1,A2 a2, 
                  generator<result_type>) {
   return _f();
 };
 result_type call(A1 a1,A2 a2, 
                  std::unary_function<arg1_type,result_type>) {
   return _f(a1);
 };
 result_type call(A1 a1,A2 a2,
                  std::binary_function<arg1_type,arg2_type,result_type>) {
   return _f(a1,a2);
 };

Ostatni argument służy do tylko do przeciążenia funkcji wykorzystanego w operatorze nawiasów:

 result_type operator()(A1 a1,A2 a2) {
   return call(a1,a2,typename functor_traits<F>::f_type());
 }; 

Jak zwykle dodajemy funkcję:

template<typename A1,typename A2,typename F> call_t2<F,A1,A2> call(F f) {
return call_t2<F,A1,A2>(f);}

Podobnie definiujemy jednoargumentową wersję tego szablonu call_t1 i przeciążoną funkcję:

template<typename A1,typename F> call_t1<F,A1> call(F f) {
return call_t1<F,A1>(f);}

Całość kodu znajduje się w pliku call.h.

Implementując adapter macro musimy umieć poznać która z dwu przekazanych funkcji ma wiecej argumentów. Używamy w tym celu szablonu If_then_else:

template<typename F1,typename F2> struct macro_type {
 typedef typename If_then_else<
   (size_t)functor_traits<F1>::n_args 
      > =        
   (size_t)functor_traits<F2>::n_args ,
   typename functor_traits<F1>::f_type,
   typename functor_traits<F2>::f_type>::Result  m_type;
};

Korzystając z macro_type i call możemy zaimplementować szablon:

template<typename F1,typename F2>  class macro_t :
public macro_type<F1,F2>::m_type {
 public:
  typedef void result_type ;

Proszę zwrócić uwagę, że dziedzicząc z macro_type<F1,F2>::m_type defiuniujemy poprawne typy argumentów fuktora, ale niekoniecznie dobry typ wartości zwracanej. Dlatego redefinujemy go potem na void. Całość kodu znajduje się w pliku mixed_macro.h.