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