2. C++ Avancé




2.10. Functors, adapters, Fonctions Lambda

2.10.1. Qu'est ce qu'un functor ?

Un foncteur (Functor en anglais) encore appelé objet-fonction est un objet pour lequel l'opérateur () a été redéfini.

Un foncteur est passé en paramètre des algorithmes de la STL afin de donner un comportement spécifique.

Voici un exemple qui trie un vector de personnes suivant leurs ages et calcule le nombre d'adultes du vecteur.

  1. #include <iostream>
  2. #include <algorithm>
  3. #include <iterator>
  4. #include <functional>
  5. #include <vector>
  6. using namespace std;
  7.  
  8. /**
  9.  * generic function used to display contents of container
  10.  */
  11. template<class Container>
  12. void print(Container& c) {
  13.     for (auto x : c) {
  14.         cerr << x << endl;
  15.     }
  16. }
  17.  
  18. #define cdump(msg, v) cerr << msg << endl << #v << " = [" << endl; \
  19.     print(v); \
  20.     cerr << "]" << endl;
  21.  
  22.  
  23. /**
  24.  * a Person
  25.  */
  26. class Person {
  27. protected:
  28.     string name;
  29.     int age;
  30.  
  31. public:
  32.     Person(string n, int a) : name(n), age(a) {
  33.     }
  34.    
  35.     friend ostream& operator<<(ostream& out, Person& p) {
  36.         out << p.name << ", " << p.age;
  37.         return out;
  38.     }
  39.    
  40.     friend ostream& operator<<(ostream& out, Person *p) {
  41.         out << p->name << ", " << p->age;
  42.         return out;
  43.     }
  44.    
  45.     int get_age() {
  46.         return age;
  47.     }
  48.    
  49.     string get_name() {
  50.         return name;
  51.     }
  52.  
  53.  
  54. };
  55.  
  56. // ==================================================================
  57. // version for Person&
  58. // ==================================================================
  59.  
  60. /**
  61.  * Functor defined as a struct that compares the ages of
  62.  * two Persons
  63.  */
  64. struct PersonSortByAgeFunctor {
  65.     bool operator()(const Person& lhs, const Person& rhs) {
  66.         return const_cast<Person&>(lhs).get_age() <
  67.             const_cast<Person&>(rhs).get_age();
  68.     }
  69. };
  70.  
  71. /**
  72.  * Functor defined as a class that compares the age of
  73.  * a Person to some limit
  74.  */
  75. class PersonAdultCounter {
  76. protected:
  77.     int limit;
  78.    
  79. public:
  80.     PersonAdultCounter(int l) : limit(l) {
  81.     }
  82.    
  83.     bool operator()(const Person& rhs) {
  84.         return const_cast<Person&>(rhs).get_age() >= limit;
  85.     }  
  86. };
  87.  
  88. /**
  89.  * version for reference to Person
  90.  */
  91. void test_person() {
  92.     // use vector of Person
  93.     vector<Person> vp;
  94.     vp.push_back(Person("donald", 40));
  95.     vp.push_back(Person("riri", 10));
  96.     vp.push_back(Person("fifi", 12));
  97.     vp.push_back(Person("picsou", 60));
  98.     vp.push_back(Person("loulou", 11));
  99.    
  100.     sort(vp.begin(), vp.end(), PersonSortByAgeFunctor());
  101.     cdump("print persons", vp);
  102.  
  103.     int n_adults = count_if(vp.begin(), vp.end(), PersonAdultCounter(18));
  104.     cerr << "there are " << n_adults << " adults" << endl;
  105. }
  106.  
  107. // ==================================================================
  108. // version for Person *
  109. // ==================================================================
  110.  
  111. /**
  112.  * Functor defined as a struct that compares the ages of
  113.  * two pointers to Persons
  114.  */
  115. struct PtrPersonSortByAgeFunctor {
  116.     bool operator()(const Person *lhs, const Person *rhs) {
  117.         return const_cast<Person *>(lhs)->get_age() <
  118.             const_cast<Person *>(rhs)->get_age();
  119.     }
  120. };
  121.  
  122. /**
  123.  * Functor defined as a class that compares the age of
  124.  * a pointer to Person to some limit
  125.  */
  126. class PtrPersonAdultCounter {
  127. protected:
  128.     int limit;
  129.    
  130. public:
  131.     PtrPersonAdultCounter(int l) : limit(l) {
  132.     }
  133.    
  134.     bool operator()(const Person *rhs) {
  135.         return const_cast<Person *>(rhs)->get_age() >= limit;
  136.     }  
  137. };
  138.  
  139. /**
  140.  * version for pointer of Person
  141.  */
  142. void test_ptr_of_person() {
  143.     // use vector of Person *
  144.     vector<Person *> vpp;
  145.     vpp.push_back(new Person("donald", 40));
  146.     vpp.push_back(new Person("riri", 10));
  147.     vpp.push_back(new Person("fifi", 12));
  148.     vpp.push_back(new Person("picsou", 60));
  149.     vpp.push_back(new Person("loulou", 11));
  150.    
  151.     sort(vpp.begin(), vpp.end(), PtrPersonSortByAgeFunctor());
  152.     cdump("print persons", vpp);
  153.  
  154.     int n_adults = count_if(vpp.begin(), vpp.end(), PtrPersonAdultCounter(18));
  155.     cerr << "there are " << n_adults << " adults" << endl;
  156. }
  157.  
  158.  
  159. /**    
  160.  * main function
  161.  */    
  162. int main() {
  163.     test_person();
  164.     test_ptr_of_person();      
  165.     return 0;
  166. }
  167.  
  168.  
print persons
vp = [
riri, 10
loulou, 11
fifi, 12
donald, 40
picsou, 60
]
there are 2 adults
print persons
vpp = [
riri, 10
loulou, 11
fifi, 12
donald, 40
picsou, 60
]
there are 2 adults

2.10.2. Qu'est ce qu'un adapter ?

Un Adaptateur (Adapter en anglais) sont des foncteurs de la STL

On distingue 4 types d'adaptateurs :

Binders
convertissent les objets binaires en objets unaires en fixant l'un des arguments de () à une constante par exemple bind1st, bind2nd
Negators
modifient le comportement en inversant le sens (ex: not1, not2)
Adaptors for pointers to functions
permettent aux pointeurs vers des fonctions unaires ou binaire de fonctionner avec les adaptateurs de la STL (ex: pointer_to_binary_function)
Adaptors for pointers to member functions
permettent aux pointeur sur des fonctions membres unaires ou binaires de fonctionner avec les adatateurs de la STL (ex: mem_fun_t)
  1. #include <iostream>
  2. #include <algorithm>
  3. #include <iterator>
  4. #include <functional>
  5. #include <vector>
  6. using namespace std;
  7.  
  8. /**
  9.  * generic function used to display contents of container
  10.  */
  11. template<class Container>
  12. void print(Container& c) {
  13.     for (auto x : c) {
  14.         cerr << x << " ";
  15.     }
  16.     cerr << endl;
  17. }
  18.  
  19. #define cdump(msg, v) cerr << msg << endl << #v << " = "; print(v);
  20.  
  21. /**
  22.  * a Person: user-defined class
  23.  */
  24. class Person {
  25. protected:
  26.     string name;
  27.     int age;
  28.  
  29. public:
  30.     Person(string n, int a) : name(n), age(a) {
  31.     }
  32.    
  33.     friend ostream& operator<<(ostream& out, Person& p) {
  34.         out << p.name << ", " << p.age;
  35.         return out;
  36.     }
  37.    
  38.     friend ostream& operator<<(ostream& out, Person *p) {
  39.         out << p->name << ", " << p->age;
  40.         return out;
  41.     }
  42.    
  43.     int get_age() {
  44.         return age;
  45.     }
  46.    
  47.     string get_name() {
  48.         return name;
  49.     }
  50.    
  51.     void print() {
  52.         cout << name << endl;
  53.     }
  54.    
  55. };
  56.  
  57. /**
  58.  * user defined function that returns -x given x
  59.  */
  60. int my_negate(int x) {
  61.     return -x;
  62. }
  63.  
  64. /**
  65.  * main function
  66.  */
  67. int main() {
  68.     vector<int> v = { 1, -2, 3, -4, 5, -6 };
  69.     cdump("initial v", v);
  70.    
  71.     // use of negate
  72.     transform(v.begin(), v.end(), v.begin(), negate<int>());
  73.     // or
  74.     transform(v.begin(), v.end(), v.begin(), my_negate);
  75.     cdump("use of adapter.negator ", v);
  76.    
  77.     // use of multiplies
  78.     transform(v.begin(), v.end(),   // source
  79.           v.begin(),            // second source
  80.           v.begin(),            // destination
  81.           multiplies<int>());   // operation
  82.     cdump("use of adapter.multiply ", v);
  83.    
  84.     // use of bind2nd and greater
  85.     // replace each value greater than 20 by the value -5
  86.     replace_if(v.begin(), v.end(),
  87.            bind2nd(greater<int>(), 20), -5);
  88.     cdump("use of adapter.bind2nd ", v);  
  89.    
  90.     // use of mem_fun_ref for vector of Person
  91.     vector<Person> vp;
  92.     vp.push_back(Person("riri", 10));
  93.     vp.push_back(Person("fifi", 12));
  94.     vp.push_back(Person("loulou", 11));
  95.    
  96.     for_each(vp.begin(), vp.end(), std::mem_fun_ref(&Person::print));
  97.     cdump("print names", vp);
  98.  
  99.     // use of mem_fun_ for vector of pointer to Person
  100.     vector<Person *> vpp;
  101.     vpp.push_back(new Person("riri", 10));
  102.     vpp.push_back(new Person("fifi", 12));
  103.     vpp.push_back(new Person("loulou", 11));
  104.  
  105.     for_each(vpp.begin(), vpp.end(), std::mem_fun(&Person::print));
  106.     cdump("print names", vpp);
  107.        
  108.     return 0;
  109. }
  110.  
  111.  
initial v
v = 1 -2 3 -4 5 -6 
use of adapter.negator 
v = -1 2 -3 4 -5 6 
use of adapter.multiply 
v = 1 4 9 16 25 36 
use of adapter.bind2nd 
v = 1 4 9 16 -5 -5 
riri
fifi
loulou
print names
vp = riri, 10 fifi, 12 loulou, 11 
riri
fifi
loulou
print names
vpp = riri, 10 fifi, 12 loulou, 11

2.10.3. Qu'est ce qu'une fonction lambda ?

Il s'agit d'un mécanisme qui permet de déclarer une fonction localement au moment où on en a besoin.

La fonction lambda n'a pas de nom puisque locale.

2.10.3.a  Déclarer une fonction lambda

[capture_block](parameters) mutable exception -> return_type { body }

En ce qui concerne la capture, on dispose de deux possibilités qui peuvent ensuite être combinées et énumérées sur les variables :

Par exemple :

2.10.3.b  Utiliser une fonction lambda

// définition mais non appelee
[] { cout << "hello" << endl; };
// définie et appelée
[] { cout << "hello" << endl; } ();

auto dbl = [](const int n){ return 2*n; };
// au lieu de
// int dbl(const int n) { return 2*n; };
int a = dbl(1);
int b = dbl(2);

Un autre exemple lié à la STL :

  1. #include <vector>
  2. #include <iterator>
  3. #include <algorithm>
  4. #include <iostream>
  5. using namespace std;
  6.  
  7. int main() {
  8.     vector<int> v(10);
  9.    
  10.     int index = 0;
  11.  
  12.     // generate vector elements with lambda function
  13.     // incrementing index an extern variable
  14.     generate(v.begin(), v.end(), [&index]() {
  15.         return index++;
  16.     });
  17.  
  18.     // print vector contents
  19.     cout << "v = [";
  20.     copy(v.begin(), v.end(), ostream_iterator<int>(cout," "));
  21.     cout << "]" << endl;
  22.    
  23.    
  24.     // sort in reverse order with lambda function
  25.     sort(v.begin(), v.end(), [](int a, int b) -> bool {
  26.         return a > b;
  27.     });
  28.    
  29.    
  30.     // print vector contents using for_each
  31.     cout << "v = (sorted) [";
  32.     for_each(v.begin(), v.end(), [](int i){
  33.         cout << i << " ";
  34.     });
  35.     cout << "]" << endl;
  36.    
  37.     return 0;
  38. }
  39.  
  40.  
v = [0 1 2 3 4 5 6 7 8 9 ]
v = (sorted) [9 8 7 6 5 4 3 2 1 0 ]
\begin{verbatim} vector v(10); int index=0; generate(v.begin(), v.end(), [&index]() {return index++;}); for_each(v.begin(), v.end(), [](int i){ cout << i << " "; }); // au lieu de copy(v.begin(), v.end(), ostream_iterator(cout," ")); \end{verbatim}