Aider le compilateur C ++ à résoudre la surcharge de fonctions

Dans certains cas, les compilateurs C ++ ne peuvent pas sélectionner une fonction surchargée appropriée, par exemple, dans une situation qui est évidente d'un point de vue humain - une erreur de compilation se produit:

void f(int i){} void f(string s){} vector<int> int_c = { 1, 2, 3, 4, 5 }; vector<string> string_c = { "Sic" ,"transit" ,"gloria" ,"mundi" }; for_each(begin(int_c), end(int_c), f);//error C2672: "for_each":      

Le problème est le manque de paramètres pour f dans la dernière ligne, sur lesquels le compilateur pourrait résoudre la surcharge. Le fait que des itérateurs d'un type très spécifique soient passés aux paramètres for_each n'a pas d'importance pour le compilateur.

Vous pouvez proposer plusieurs méthodes "frontales" pour résoudre le problème:

1) Utilisation de static_cast <> () pour forcer la conversion vers le pointeur de fonction du type souhaité.

Nous jetons le pointeur sur void f (int i):

 std::for_each(begin(int_c), end(int_c), static_cast<void (*)(int)>(&f)); 

Nous transtypons le pointeur vers void f (chaîne i):

 std::for_each(begin(string_c), end(string_c), static_cast<void (*)(string)>(&f)); 

2) Définition des pointeurs vers les fonctions et leur utilisation.

Nous utilisons void f (int i):

  void (*fp_int)(int) = &f; for_each(begin(int_c), end(int_c), fp_int); 

Nous utilisons void f (string i):

 void (*fp_string)(string) = &f; for_each(begin(string_c), end(string_c), fp_string); 

3) Création de spécialisations explicites pour la fonction de modèle for_each:

Nous utilisons void f (int i):

 std::for_each<vector<int>::const_iterator, void(*)(int) >(int_c.begin(), int_c.end(), f); 

Nous utilisons void f (string i):

 std::for_each<vector<string>::const_iterator, void(*)(string) >(string_c.begin(), string_c.end(), f); 

ou plus compact:

 std::for_each<decltype(int_c.begin()), void(*)(int) >(int_c.begin(), int_c.end(), f); std::for_each<decltype(string_c.begin()), void(*)(string) >(string_c.begin(), string_c.end(), f); 

Les méthodes proposées ci-dessus ne sont pas universelles et conduisent à la nécessité de développer du code supplémentaire côté appelant. La meilleure idée est de créer une coque mince sur la fonction f qui prend les paramètres de la fonction f et les lui transmet sans modifications.

Il faut également considérer les possibilités:

  • l'existence d'un nombre indéfini de paramètres de la fonction f,
  • la présence de la valeur de retour de la fonction,
  • passage de paramètres par référence,
  • l'existence de qualificatifs de paramètres cv,
  • exceptions dans la fonction f.

Nous analyserons pas à pas la construction d'une telle coque. La fonction lambda est une bonne base. Avec le support de la norme C ++ 11 par le compilateur, cela peut être implémenté comme suit:

 for_each(begin(int_c), end(int_c), [](int a) { return f(a); }); for_each(begin(string_c), end(string_c), [](string a) { return f(a); }); 

ou en utilisant decltype ():

 for_each(begin(int_c), end(int_c), [](decltype(*int_c.begin()) a) { return f(a); }); for_each(begin(string_c), end(string_c), [](decltype(*string_c.begin()) a) { return f(a); }); 

Avec le support de la norme C ++ 14 par le compilateur, nous avons la solution suivante pour un lambda polymorphe:

 auto l = [](auto a) { return f(a); }; for_each(begin(int_c), end(int_c), l); for_each(begin(string_c), end(string_c), l); 

Donc, nous passons le paramètre à la fonction f comme paramètre au lambda, puis nous appelons f avec ce paramètre. Mais il y a un problème: on ne sait pas à l'avance - les paramètres de la fonction f sont-ils des valeurs ou des références? Par conséquent, vous devez utiliser l'équipement parfait.

 auto l = [](auto&& a) { return f(std::forward<decltype(a)>(a)); }; for_each(begin(int_c), end(int_c), l); for_each(begin(string_c), end(string_c), l); 

Sur le sujet de la transmission parfaite (transmission parfaite) se trouve dans Meyers [1]. Une version plus compréhensible (pour moi en tout cas) de la présentation du même matériel peut être trouvée dans l'article [2], dont la traduction est sur Habré [3].

Pour un nombre indéfini de paramètres de fonction, nous indiquons les paramètres lambda comme variables et appelons std :: forward pour chaque paramètre passé à f.

  auto l = [](auto&&... a) { return f(std::forward<decltype(a)>(a)...); }; 

Nous ajoutons le spécificateur de temps du compilateur noexcept [4] et l'opérateur noexcept [5] pour indiquer au compilateur si la fonction f lèvera des exceptions [6].

  auto l = [](auto&&... a) \ noexcept(noexcept(f(std::forward<decltype(a)>(a)...)))\ { return f(std::forward<decltype(a)>(a)...); }; 

Ajoutez la sortie du type de valeur de retour pour le lambda construit.

  auto l = [](auto&&... a) \ noexcept(noexcept(f(std::forward<decltype(a)>(a)...)))\ -> decltype(f(std::forward<decltype(a)>(a)...))\ { return f(std::forward<decltype(a)>(a)...); }; 

Si la répétition de code triple est ennuyeuse et nous n'hésitons pas à utiliser des macros - vous pouvez réduire la taille du côté appelant.

 #define LIFT(foo) \ [](auto&&... x) \ noexcept(noexcept(foo(std::forward<decltype(x)>(x)...))) \ -> decltype(foo(std::forward<decltype(x)>(x)...)) \ { return foo(std::forward<decltype(x)>(x)...); } 

Par conséquent, nous avons un shell qui aide le compilateur à résoudre une fonction surchargée.

 for_each(begin(int_c), end(int_c), LIFT(f)); for_each(begin(string_c), end(string_c), LIFT(f)); 

[1] S. Meyers «C ++ moderne efficace» Point 24: Distinguer les références universelles des références de valeur.
[2] eli.thegreenplace.net/2014/perfect-forwarding-and-universal-references-in-c
[3] habr.com/en/post/242639
[4] en.cppreference.com/w/cpp/language/noexcept_spec
[5] en.cppreference.com/w/cpp/language/noexcept
[6] habr.com/en/post/164221
[7] www.fluentcpp.com/2017/08/01/overloaded-functions-stl
[8] www.youtube.com/watch?v=I3T4lePH-yA

Source: https://habr.com/ru/post/fr485854/


All Articles