Zaawansowane CPP/Ćwiczenia 11: Funktory: Różnice pomiędzy wersjami

Z Studia Informatyczne
Przejdź do nawigacjiPrzejdź do wyszukiwania
Mirek (dyskusja | edycje)
Nie podano opisu zmian
Mirek (dyskusja | edycje)
Nie podano opisu zmian
Linia 4: Linia 4:
realizujący złożenie dwuargumentowe <math>\displaystyle f(g(x),h(y))</math>.
realizujący złożenie dwuargumentowe <math>\displaystyle f(g(x),h(y))</math>.
}}
}}
<div class="mw-collapsible mw-made=collapsible mw-collapsed"><span class="mw-collapsible-toogle mw-collapsible-toogle-default style="font-variant:small-caps">Rozwiązanie</span><div class="mw-collapsible-content" style="display:none">
Zobacz plik {mod10/exercises/compose_f_gx_hy.h}<tt>composefgxhy.h</tt>.
</div></div>
{{cwiczenie|2||
{{cwiczenie|2||


Linia 67: Linia 71:
</div></div>
</div></div>


<div class="mw-collapsible mw-made=collapsible mw-collapsed"><span class="mw-collapsible-toogle mw-collapsible-toogle-default style="font-variant:small-caps">Rozwiązanie</span><div class="mw-collapsible-content" style="display:none">
Nierekurencyjna funkcja przechodząca drzewo wgłąb może wyglądać następująco:


std::deque<node *> nodes;
nodes.push_back(_root);<br>
while(!nodes.empty()) {
  node *nd <nowiki> =</nowiki>    nodes.back();
  nodes.pop_back();<br>
  if(node->right) nodes.push_back(node->right);
  if(node->left)  nodes.push_back(node->left);
}


Iterator będzie wykonywał ten algorytm krok po kroku. W każdym kroku
węzeł <tt>nd</tt> będzie wezłem wskazywanym przez ten iterator.
Jako oznaczenie końca iteracji wykorzystamy iterator wskazujący na węzeł pusty.


Definujemy klasę zagnieżdżoną wewnątrz <tt>binary_tree</tt>:


class iterator {
std::deque<node *> _nodes;
node *_current;<br>
iterator(node *ptr <nowiki> =</nowiki>    0):_current(ptr) {
  if(_current) {
    if(_current->right)
        _nodes.push_back(_current->right);
    if(_current->left)
        _nodes.push_back(_current->left);
}<br>
iterator &operator++() {
  _current<nowiki> =</nowiki>  _nodes.back();
  _nodes.pop_back();<br>
  if(_current) {
    if(_current->right)
        _nodes.push_back(_current->right);
    if(_current->left)
        _nodes.push_back(_current->left);
    }
    else
    _current<nowiki> =</nowiki>  0;
}


Żeby ta klasa zachowywała się jak wskaźnik dodajemy operatory:
 
 
 
 
 
 
 
 
 
 
''Uwaga: przekonwertowane latex2mediawiki; prawdopodobnie trzeba wprowadzi� poprawki''
 
{Funktory}
 
'''Rozwiązanie 1 '''
 
 
 
'''Rozwiązanie 2 '''
 
  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);
  }
};
    
    
public:
Dzięki konkretyzacji na żądanie wygenerowany zostanie tylko ten
    T &operator*() {return _ptr->_val;};
którego użyjemy w kodzie.
    T *operator->() {return &(_ptr->_val);};
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;  
};


Potrzebny jest też operator porównania:
template<typename A1,typename R>
struct bind1st_f_type<std::unary_function<A1,R> > {
  typedef generator<R> f_type;
};
   
   
  bool operator<nowiki> =</nowiki>  <nowiki> =</nowiki>   (const trivial_iterator&lhs) const {
Całość klodu znajduje się w pliku {mod10/exercises/bind.h}<tt>bind.h</tt>.
    return this->_ptr<nowiki> =</nowiki>  <nowiki> =</nowiki>  lhs._ptr;
 
  }  
'''Rozwiązanie 3 '''
  bool operator!<nowiki> =</nowiki>   (const trivial_iterator&lhs) const {
 
    return !operator<nowiki> =</nowiki>  <nowiki> =</nowiki>  (lhs);
Zobacz plik {mod10/exercises/macro.h}<tt>macro.h</tt>.
  }
 
'''Rozwiązanie 4 '''


Potrzebna jest jeszcze deklaracja:
Zaczynamy od implemenatcji adaptera {call}. W tym celu defiuniujemy
szablon:
   
   
friend class binary_tree;  
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;


żeby klasa <tt>binary_tree</tt> mogła korzystać z konstruktora
  F _f;
<tt>iterator(node *ptr)</tt>.  
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 {mod10/exercises/call.h}<tt>call.h</tt>.


W klasie <tt>binary_tree</tt> definiujemy funkcje:
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}:


   iterator begin() {return iterator(_root);}
template<typename F1,typename F2> struct macro_type {
   iterator end(){return iterator(0);}
   typedef typename If_then_else<
</div></div>
    (size_t)functor_traits<F1>::n_args
      ><nowiki> =</nowiki>       
    (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
{mod10/exercises/mixed_macro.h}<tt>mixedmacro.h</tt>.

Wersja z 10:40, 25 wrz 2006

Ćwiczenie 1

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

Rozwiązanie

Ć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.

Ć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);

Ć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ź









Uwaga: przekonwertowane latex2mediawiki; prawdopodobnie trzeba wprowadzi� poprawki

{Funktory}

Rozwiązanie 1


Rozwiązanie 2

 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ść klodu znajduje się w pliku {mod10/exercises/bind.h}bind.h.

Rozwiązanie 3

Zobacz plik {mod10/exercises/macro.h}macro.h.

Rozwiązanie 4

Zaczynamy od implemenatcji adaptera {call}. W tym celu defiuniujemy 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 {mod10/exercises/call.h}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 {mod10/exercises/mixed_macro.h}mixedmacro.h.