Helfen Sie dem C ++ - Compiler, die Funktionsüberladung zu beheben

In einigen Fällen können C ++ - Compiler keine geeignete überladene Funktion auswählen, z. B. in einer Situation, die aus menschlicher Sicht offensichtlich ist - ein Kompilierungsfehler tritt auf:

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

Das Problem ist das Fehlen von Parametern für f in der letzten Zeile, in der der Compiler die Überladung beheben könnte. Die Tatsache, dass Iteratoren eines ganz bestimmten Typs für jeden Parameter übergeben werden, spielt für den Compiler keine Rolle.

Sie können verschiedene "frontale" Möglichkeiten anbieten, um das Problem zu lösen:

1) Verwenden Sie static_cast <> (), um die Umwandlung zum Funktionszeiger des gewünschten Typs zu erzwingen.

Wir werfen auf den Zeiger, um f (int i) zu löschen:

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

Wir werfen den Zeiger auf void f (string i):

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

2) Definition von Zeigern auf Funktionen und deren Verwendung.

Wir verwenden void f (int i):

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

Wir verwenden void f (string i):

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

3) Erstellen expliziter Spezialisierungen für die Vorlagenfunktion for_each:

Wir verwenden void f (int i):

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

Wir verwenden void f (string i):

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

oder kompakter:

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

Die oben vorgeschlagenen Methoden sind nicht universell und führen dazu, dass auf der aufrufenden Seite zusätzlicher Code entwickelt werden muss. Die beste Idee ist, eine dünne Hülle über der Funktion f zu erstellen, die die Parameter der Funktion f übernimmt und ohne Änderungen an diese übergibt.

Es ist auch notwendig, die Möglichkeiten zu berücksichtigen:

  • die Existenz einer unbestimmten Anzahl von Parametern der Funktion f,
  • das Vorhandensein des Rückgabewerts der Funktion,
  • Übergabe von Parametern als Referenz,
  • die Existenz von Lebenslaufparameter-Qualifikatoren,
  • Ausnahmen in Funktion f.

Schritt für Schritt werden wir den Aufbau einer solchen Hülle analysieren. Die Lambda-Funktion ist eine gute Grundlage. Mit der Unterstützung des C ++ 11-Standards durch den Compiler kann dies folgendermaßen implementiert werden:

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

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

Mit der Unterstützung des C ++ 14-Standards durch den Compiler haben wir die folgende Lösung für ein polymorphes Lambda:

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

Wir übergeben also den Parameter an die Funktion f als Parameter an das Lambda und rufen dann f mit diesem Parameter auf. Aber es gibt ein Problem: Wir wissen nicht im Voraus - sind die Parameter der Funktion f Werte oder Referenzen? Daher müssen Sie die perfekte Ausrüstung verwenden.

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

Zum Thema perfekte Übertragung (Perfect Forwarding) findet sich Meyers [1]. Eine (für mich jedenfalls) verständlichere Version der Präsentation desselben Materials findet sich in Artikel [2], dessen Übersetzung auf Habré [3] ist.

Für eine unbestimmte Anzahl von Funktionsparametern geben wir die Lambda-Parameter als variabel an und rufen für jeden an f übergebenen Parameter std :: forward auf.

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

Wir fügen den Compiler-Zeitspezifizierer noexcept [4] und den noexcept-Operator [5] hinzu, um dem Compiler anzuzeigen, ob die Funktion f Ausnahmen auslöst [6].

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

Addieren Sie die Ausgabe des Rückgabewerttyps für das konstruierte Lambda.

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

Wenn die dreifache Codewiederholung ärgerlich ist und wir nicht zögern, Makros zu verwenden, können Sie die Größe auf der aufrufenden Seite reduzieren.

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

Als Ergebnis haben wir eine Shell, die dem Compiler hilft, eine überladene Funktion aufzulösen.

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

[1] S. Meyers "Effektives modernes C ++" Punkt 24: Unterscheiden Sie universelle Referenzen von rWertreferenzen.
[2] eli.thegreenplace.net/2014/perfect-forwarding-and-universal-references-in-c
[3] habr.com/de/post/242639
[4] de.cppreference.com/w/cpp/language/noexcept_spec
[5] de.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/de485854/


All Articles