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
m Zastępowanie tekstu – „\displaystyle ” na „”
 
(Nie pokazano 15 wersji utworzonych przez 2 użytkowników)
Linia 2: Linia 2:


Zaimplementuj adapter <code><nowiki>compose_f_gx_hy</nowiki></code>
Zaimplementuj adapter <code><nowiki>compose_f_gx_hy</nowiki></code>
realizujący złożenie dwuargumentowe <math>\displaystyle f(g(x),h(y))</math>.
realizujący złożenie dwuargumentowe <math>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 [[media:Compose_f_gx_hy.h | compose_f_gx_hy.h]].
</div></div>
{{cwiczenie|2||
{{cwiczenie|2||


Linia 9: Linia 14:
zaimplementuj adpter <code><nowiki>bind1st</nowiki></code>, który bedzie działał zarówno dla funktorów jedno-, jak i dwuargumentowych.
zaimplementuj adpter <code><nowiki>bind1st</nowiki></code>, który bedzie działał zarówno dla funktorów jedno-, jak i dwuargumentowych.
}}
}}
<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">
W klasie <tt>binder1st</tt> 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;<br> 
  const bind_type _val;
  F _op;
  public:
  binder1st(F op,bind_type val):_op(op), _val(val) {};<br>
  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 <tt>bind1st_f_type</tt> 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;<br>
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;
};<br>
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 [[media:Bind.h | bind.h]].
</div></div>
{{cwiczenie|3||  
{{cwiczenie|3||  


Linia 28: Linia 67:
  f1(x);f2(x);f3(x);
  f1(x);f2(x);f3(x);
}}
}}
<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 [[media:Macro.h | macro.h]].
</div></div>
{{cwiczenie|4||   
{{cwiczenie|4||   


Linia 68: Linia 112:


<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">
<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:
Zaczynamy od implemenatcji adaptera <tt>call</tt>. 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;<br>
  F _f;<br>
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);
  };


std::deque<node *> nodes;
Ostatni argument służy do tylko do przeciążenia funkcji wykorzystanego w operatorze nawiasów:
  nodes.push_back(_root);<br>
   
while(!nodes.empty()) {
  result_type operator()(A1 a1,A2 a2) {
  node *nd <nowiki> =</nowiki>    nodes.back();
    return call(a1,a2,typename functor_traits<F>::f_type());
  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
Jak zwykle dodajemy funkcję:
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.
template<typename A1,typename A2,typename F> call_t2<F,A1,A2> call(F f) {
return call_t2<F,A1,A2>(f);}


Definujemy klasę zagnieżdżoną wewnątrz <tt>binary_tree</tt>:
Podobnie definiujemy jednoargumentową wersję tego szablonu <tt>call_t1</tt> i przeciążoną funkcję:


  class iterator {
  template<typename A1,typename F> call_t1<F,A1> call(F f) {
std::deque<node *> _nodes;
  return call_t1<F,A1>(f);}
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:
Całość kodu znajduje się w pliku [[media:Call.h | call.h]].
 
public:
    T &operator*()  {return _ptr->_val;};
    T *operator->() {return &(_ptr->_val);};


Potrzebny jest też operator porównania:
Implementując adapter <tt>macro</tt> musimy umieć poznać która z dwu przekazanych funkcji ma wiecej argumentów. Używamy w tym celu szablonu <tt>If_then_else</tt>:
  bool operator<nowiki> =</nowiki>  <nowiki> =</nowiki>  (const trivial_iterator&lhs) const {
    return this->_ptr<nowiki> =</nowiki>  <nowiki> =</nowiki>   lhs._ptr;
  }
  bool operator!<nowiki> =</nowiki>   (const trivial_iterator&lhs) const {
    return !operator<nowiki> =</nowiki>  <nowiki> =</nowiki>  (lhs);
  }


Potrzebna jest jeszcze deklaracja:
template<typename F1,typename F2> struct macro_type {
   
  typedef typename If_then_else<
  friend class binary_tree;  
    (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;
  };


żeby klasa <tt>binary_tree</tt> mogła korzystać z konstruktora
Korzystając z <tt>macro_type</tt> i <tt>call</tt> możemy zaimplementować szablon:
<tt>iterator(node *ptr)</tt>.


W klasie <tt>binary_tree</tt> definiujemy funkcje:
template<typename F1,typename F2> class macro_t :
public macro_type<F1,F2>::m_type {
  public:
  typedef void result_type ;


  iterator begin() {return iterator(_root);}
Proszę zwrócić uwagę, że dziedzicząc z <tt>macro_type<F1,F2>::m_type</tt>
  iterator end(){return iterator(0);}
defiuniujemy poprawne typy argumentów fuktora, ale niekoniecznie dobry
typ wartości zwracanej. Dlatego redefinujemy go potem na <tt>void</tt>.
Całość kodu znajduje się w pliku [[media:Mixed_macro.h | mixed_macro.h]].
</div></div>
</div></div>

Aktualna wersja na dzień 09:00, 28 sie 2023

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

Rozwiązanie

Ć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

Ć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ź
Rozwiązanie