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