Ayuda al compilador de C ++ a resolver la sobrecarga de funciones

En algunos casos, los compiladores de C ++ no pueden seleccionar una función sobrecargada adecuada, por ejemplo, en una situación que es obvia desde un punto de vista humano: se produce un error de compilación:

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":      

El problema es la falta de parámetros para f en la última línea, en la cual el compilador podría resolver la sobrecarga. El hecho de que se pasen iteradores de un tipo muy específico para cada parámetro no importa para el compilador.

Puede ofrecer varias formas "frontales" para resolver el problema:

1) Usar static_cast <> () para forzar la conversión al puntero de función del tipo deseado.

Echamos al puntero para anular f (int i):

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

Lanzamos al puntero para anular f (cadena i):

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

2) Definición de punteros a funciones y su uso.

Utilizamos void f (int i):

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

Utilizamos void f (cadena i):

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

3) Crear especializaciones explícitas para la función de plantilla for_each:

Utilizamos void f (int i):

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

Utilizamos void f (cadena i):

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

o más compacto:

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

Los métodos propuestos anteriormente no son universales y conducen a la necesidad de desarrollar código adicional en el lado de la llamada. La mejor idea es crear un caparazón delgado sobre la función f que tome los parámetros de la función f y se los pase sin cambios.

También es necesario considerar las posibilidades:

  • la existencia de un número indefinido de parámetros de la función f,
  • la presencia del valor de retorno de la función,
  • pasar parámetros por referencia,
  • la existencia de calificadores de parámetros cv,
  • excepciones en la función f.

Paso a paso analizaremos la construcción de tal caparazón. La función Lambda es una buena base. Con el soporte del estándar C ++ 11 por parte del compilador, esto se puede implementar de la siguiente manera:

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

o usando 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); }); 

Con el soporte del estándar C ++ 14 por parte del compilador, tenemos la siguiente solución para una lambda polimórfica:

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

Entonces, pasamos el parámetro a la función f como un parámetro a la lambda, y luego llamamos a f con este parámetro. Pero hay un problema: no lo sabemos de antemano: ¿son los parámetros de la función f valores o referencias? Por lo tanto, debes usar el equipo perfecto.

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

Sobre el tema de la transmisión perfecta (reenvío perfecto) se puede encontrar en Meyers [1]. Una versión más comprensible (para mí, de todos modos) de la presentación del mismo material se puede encontrar en el artículo [2], cuya traducción está en Habré [3].

Para un número indefinido de parámetros de función, indicamos los parámetros lambda como variables y llamamos std :: forward para cada parámetro pasado a f.

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

Agregamos el especificador de tiempo del compilador noexcept [4] y el operador noexcept [5] para indicar al compilador si la función f arrojará excepciones [6].

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

Agregue la salida del tipo de valor de retorno para el lambda construido.

  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 repetición de código triple es molesta y no dudamos en usar macros, puede reducir el tamaño en el lado de la llamada.

 #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)...); } 

Como resultado, tenemos un shell que ayuda al compilador a resolver una función sobrecargada.

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

[1] S. Meyers "C ++ moderno eficaz" Artículo 24: Distinguir referencias universales de referencias de valor.
[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/485854/


All Articles