Bantu kompiler C ++ dalam mengatasi kelebihan fungsi

Dalam beberapa kasus, kompiler C ++ tidak dapat memilih fungsi kelebihan beban yang sesuai, misalnya, dalam situasi yang jelas dari sudut pandang manusia - kesalahan kompilasi terjadi:

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

Masalahnya adalah kurangnya parameter untuk f di baris terakhir, di mana kompiler dapat mengatasi kelebihan beban. Fakta bahwa iterator dari tipe yang sangat spesifik diberikan untuk parameter for_each tidak masalah untuk kompiler.

Anda dapat menawarkan beberapa cara "frontal" untuk menyelesaikan masalah:

1) Menggunakan static_cast <> () untuk memaksa melemparkan ke pointer fungsi dari tipe yang diinginkan.

Kami melemparkan ke pointer untuk membatalkan f (int i):

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

Kami melemparkan ke pointer untuk membatalkan f (string i):

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

2) Definisi pointer ke fungsi dan penggunaannya.

Kami menggunakan void f (int i):

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

Kami menggunakan void f (string i):

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

3) Membuat spesialisasi eksplisit untuk fungsi templat for_each:

Kami menggunakan void f (int i):

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

Kami menggunakan void f (string i):

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

atau lebih kompak:

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

Metode yang diusulkan di atas tidak universal dan mengarah pada kebutuhan untuk mengembangkan kode tambahan di sisi panggilan. Ide terbaik adalah membuat shell tipis di atas fungsi f yang mengambil parameter fungsi f dan meneruskannya ke sana tanpa perubahan.

Penting juga untuk mempertimbangkan kemungkinan-kemungkinan:

  • keberadaan sejumlah parameter fungsi f,
  • keberadaan nilai kembali fungsi,
  • melewati parameter dengan referensi,
  • keberadaan kualifikasi parameter cv,
  • pengecualian dalam fungsi f.

Langkah demi langkah kita akan menganalisis pembangunan shell seperti itu. Fungsi Lambda adalah fondasi yang baik. Dengan dukungan standar C ++ 11 oleh kompiler, ini dapat diimplementasikan sebagai berikut:

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

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

Dengan dukungan standar C ++ 14 oleh kompiler, kami memiliki solusi berikut untuk lambda polimorfik:

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

Jadi, kita meneruskan parameter ke fungsi f sebagai parameter ke lambda, dan kemudian kita memanggil f dengan parameter ini. Tetapi ada masalah: kita tidak tahu sebelumnya - apakah parameter dari fungsi f nilai atau referensi? Karena itu, Anda harus menggunakan gigi yang sempurna.

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

Pada topik transmisi sempurna (forwarding sempurna) dapat ditemukan di Meyers [1]. Versi presentasi yang lebih jelas (bagi saya) dari materi yang sama dapat ditemukan dalam artikel [2], yang terjemahannya ada di HabrΓ© [3].

Untuk jumlah parameter fungsi yang tidak terbatas, kami mengindikasikan parameter lambda sebagai variabel mampu dan memanggil std :: forward untuk setiap parameter yang diteruskan ke f.

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

Kami menambahkan specifier waktu speciler noexcept [4] dan operator noexcept [5] untuk mengindikasikan kepada kompiler apakah fungsi f akan melempar pengecualian [6].

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

Tambahkan output dari tipe nilai pengembalian untuk lambda yang dibangun.

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

Jika pengulangan kode tiga menjengkelkan dan kami tidak ragu untuk menggunakan makro - Anda dapat mengurangi ukuran pada sisi panggilan.

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

Akibatnya, kami memiliki shell yang membantu compiler untuk menyelesaikan fungsi kelebihan beban.

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

[1] S. Meyers β€œC ++ modern yang efektif” Butir 24: Membedakan referensi universal dari referensi nilai.
[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/id485854/


All Articles