Ajude o compilador C ++ na resolução de sobrecarga de função

Em alguns casos, os compiladores C ++ não podem selecionar uma função sobrecarregada adequada, por exemplo, em uma situação óbvia do ponto de vista humano - ocorre um erro de compilação:

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

O problema é a falta de parâmetros para f na última linha, na qual o compilador pode resolver a sobrecarga. O fato de iteradores de um tipo muito específico serem transmitidos para parâmetros_each não importa para o compilador.

Você pode oferecer várias maneiras "frontais" para resolver o problema:

1) Usando static_cast <> () para forçar a conversão para o ponteiro de função do tipo desejado.

Lançamos para o ponteiro para anular f (int i):

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

Lançamos ao ponteiro para anular f (string i):

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

2) Definição de indicadores para funções e seu uso.

Usamos void f (int i):

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

Usamos void f (string i):

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

3) Criando especializações explícitas para a função de modelo for_each:

Usamos void f (int i):

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

Usamos void f (string i):

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

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

Os métodos propostos acima não são universais e levam à necessidade de desenvolver código adicional no lado de chamada. A melhor idéia é criar uma camada fina sobre a função f que pega os parâmetros da função f e os passa para ela sem alterações.

Também é necessário considerar as possibilidades:

  • a existência de um número indefinido de parâmetros da função f,
  • a presença do valor de retorno da função,
  • passando parâmetros por referência,
  • a existência de qualificadores de parâmetro cv,
  • exceções na função f.

Passo a passo, analisaremos a construção de uma concha desse tipo. A função Lambda é uma boa base. Com o suporte do padrão C ++ 11 pelo compilador, isso pode ser implementado da seguinte maneira:

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

Com o suporte do padrão C ++ 14 pelo compilador, temos a seguinte solução para uma 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); 

Então, passamos o parâmetro para a função f como um parâmetro para o lambda e, em seguida, chamamos f com esse parâmetro. Mas há um problema: não sabemos antecipadamente - os parâmetros da função f são valores ou referências? Portanto, você deve usar o equipamento perfeito.

 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 o tema da transmissão perfeita (encaminhamento perfeito) pode ser encontrado em Meyers [1]. Uma versão mais compreensível (para mim, pelo menos) da apresentação do mesmo material pode ser encontrada no artigo [2], cuja tradução está em Habré [3].

Para um número indefinido de parâmetros de função, indicamos os parâmetros lambda como variáveis ​​e chamamos std :: forward para cada parâmetro passado para f.

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

Nós adicionamos o especificador de tempo do compilador noexcept [4] e o operador noexcept [5] para indicar ao compilador se a função f lançará exceções [6].

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

Inclua a saída do tipo de valor de retorno para a lambda construída.

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

Se a repetição tripla de código for irritante e não hesitarmos em usar macros, você poderá reduzir o tamanho do lado que está chamando.

 #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, temos um shell que ajuda o compilador a resolver uma função sobrecarregada.

 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” Item 24: Distinguir referências universais de referências rvalue.
[2] eli.thegreenplace.net/2014/perfect-forwarding-and-universal-references-in-c
[3] habr.com/en/post/242639
[4] pt.cppreference.com/w/cpp/language/noexcept_spec
[5] pt.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/pt485854/


All Articles