Bagaimana cara menentukan penunjuk ke fungsi yang kelebihan beban?

139

Saya ingin meneruskan fungsi yang kelebihan beban ke std::for_each()algoritme. Sebagai contoh,

class A {
    void f(char c);
    void f(int i);

    void scan(const std::string& s) {
        std::for_each(s.begin(), s.end(), f);
    }
};

Saya mengharapkan kompiler untuk menyelesaikan f()dengan tipe iterator. Rupanya, itu (GCC 4.1.2) tidak melakukannya. Jadi, bagaimana saya bisa menentukan mana yang f()saya inginkan?

davka
sumber
3
en.cppreference.com/w/cpp/language/overloaded_address
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Jawaban:

139

Anda dapat menggunakan static_cast<>()untuk menentukan mana yang fakan digunakan sesuai dengan tanda tangan fungsi yang tersirat oleh tipe penunjuk fungsi:

// Uses the void f(char c); overload
std::for_each(s.begin(), s.end(), static_cast<void (*)(char)>(&f));
// Uses the void f(int i); overload
std::for_each(s.begin(), s.end(), static_cast<void (*)(int)>(&f)); 

Atau, Anda juga bisa melakukan ini:

// The compiler will figure out which f to use according to
// the function pointer declaration.
void (*fpc)(char) = &f;
std::for_each(s.begin(), s.end(), fpc); // Uses the void f(char c); overload
void (*fpi)(int) = &f;
std::for_each(s.begin(), s.end(), fpi); // Uses the void f(int i); overload

Jika fmerupakan fungsi anggota, maka Anda perlu menggunakan mem_fun, atau untuk kasus Anda, gunakan solusi yang disajikan dalam artikel Dr. Dobb ini .

In silico
sumber
1
Terima kasih! Namun, saya masih memiliki masalah, mungkin karena fakta bahwa dia f()adalah anggota kelas (lihat contoh yang diedit di atas)
davka
9
@ the_drow: Metode kedua sebenarnya jauh lebih aman, jika salah satu kelebihan beban hilang, metode pertama secara diam-diam memberikan perilaku tidak terdefinisi, sedangkan metode kedua mengatasi masalah pada waktu kompilasi.
Ben Voigt
3
@BenVoigt Hmm, saya menguji ini pada vs2010 dan tidak dapat menemukan kasus di mana static_cast tidak akan menangkap masalah pada waktu kompilasi. Ini memberi C2440 dengan "Tidak ada fungsi dengan nama ini dalam cakupan yang cocok dengan jenis target". Bisakah Anda menjelaskan?
Nathan Monteleone
5
@ Nathan: Mungkin saja aku sedang memikirkannya reinterpret_cast. Paling sering saya melihat gips gaya C digunakan untuk ini. Aturan saya hanya bahwa cast pada pointer fungsi berbahaya dan tidak perlu (seperti yang ditunjukkan cuplikan kode kedua, ada konversi implisit).
Ben Voigt
3
Untuk fungsi anggota:std::for_each(s.begin(), s.end(), static_cast<void (A::*)(char)>(&A::f));
sam-w
29

Lambdas untuk menyelamatkan! (catatan: C ++ 11 diperlukan)

std::for_each(s.begin(), s.end(), [&](char a){ return f(a); });

Atau menggunakan jenis deklarasi untuk parameter lambda:

std::for_each(s.begin(), s.end(), [&](decltype(*s.begin()) a){ return f(a); });

Dengan lambda polimorfik (C ++ 14):

std::for_each(s.begin(), s.end(), [&](auto a){ return f(a); });

Atau uraikan dengan menghapus kelebihan beban (hanya berfungsi untuk fungsi gratis):

void f_c(char i)
{
    return f(i);
}

void scan(const std::string& s)
{
    std::for_each(s.begin(), s.end(), f_c);
}
milleniumbug.dll
sumber
Hore untuk lambda! Memang, solusi yang sangat baik untuk masalah resolusi kelebihan beban. (Saya memikirkan ini juga, tetapi memutuskan untuk meninggalkannya dari jawaban saya agar tidak membuat air keruh.)
aldo
1
Lebih banyak kode untuk hasil yang sama. Saya pikir lambda tidak dibuat untuk itu.
Tomáš Zato - Kembalikan Monica
@ TomášZato Perbedaannya adalah bahwa jawaban ini berfungsi, dan yang diterima tidak (untuk contoh yang diposting oleh OP - Anda juga perlu menggunakan mem_fndan bind, yang, BTW. Juga C ++ 11). Juga, jika kita ingin benar-benar bertele-tele [&](char a){ return f(a); }adalah 28 karakter, dan static_cast<void (A::*)(char)>(&f)35 karakter.
milleniumbug
1
@ TomášZato Ini dia coliru.stacked-crooked.com/a/1faad53c4de6c233 tidak yakin bagaimana membuat ini lebih jelas
milleniumbug
19

Mengapa tidak berhasil

Saya mengharapkan kompiler untuk menyelesaikan f()dengan tipe iterator. Rupanya, itu (gcc 4.1.2) tidak melakukannya.

Akan lebih bagus jika itu masalahnya! Namun, for_eachadalah template fungsi, dideklarasikan sebagai:

template <class InputIterator, class UnaryFunction>
UnaryFunction for_each(InputIterator, InputIterator, UnaryFunction );

Pemotongan template perlu memilih jenis untuk UnaryFunctionpada saat panggilan. Tetapi ftidak memiliki tipe tertentu - ini adalah fungsi yang kelebihan beban, ada banyak fyang masing-masing memiliki tipe yang berbeda. Saat ini tidak ada cara untuk for_eachmembantu proses pemotongan template dengan menyatakan yang fdiinginkan, jadi pemotongan template gagal. Agar pemotongan template berhasil, Anda perlu melakukan lebih banyak pekerjaan di situs panggilan.

Solusi umum untuk memperbaikinya

Melompat ke sini beberapa tahun dan C ++ 14 kemudian. Daripada menggunakan a static_cast(yang akan memungkinkan pemotongan template berhasil dengan "memperbaiki" yang fingin kita gunakan, tetapi mengharuskan Anda untuk melakukan resolusi berlebih secara manual untuk "memperbaiki" yang benar), kami ingin membuat kompilator berfungsi untuk kita. Kami ingin fmengajukan beberapa argumen. Dengan cara yang paling umum, itu:

[&](auto&&... args) -> decltype(auto) { return f(std::forward<decltype(args)>(args)...); }

Itu banyak untuk diketik, tapi masalah semacam ini sering muncul menjengkelkan, jadi kita bisa membungkusnya dalam makro (menghela napas):

#define AS_LAMBDA(func) [&](auto&&... args) -> decltype(func(std::forward<decltype(args)>(args)...)) { return func(std::forward<decltype(args)>(args)...); }

dan kemudian gunakan saja:

void scan(const std::string& s) {
    std::for_each(s.begin(), s.end(), AS_LAMBDA(f));
}

Ini akan melakukan apa yang Anda inginkan dari kompilator - melakukan resolusi overload pada namanya fdan melakukan hal yang benar. Ini akan berfungsi terlepas dari apakah ffungsi bebas atau fungsi anggota.

Barry
sumber
7

Bukan untuk menjawab pertanyaan Anda, tetapi saya satu-satunya yang menemukan

for ( int i = 0; i < s.size(); i++ ) {
   f( s[i] );
}

lebih sederhana dan lebih pendek dari for_eachalternatif yang disarankan oleh in silico dalam kasus ini?

segera
sumber
2
mungkin, tapi membosankan :) juga, jika saya ingin menggunakan iterator untuk menghindari operator [], ini menjadi lebih lama ...
davka
3
@Davka Boring adalah yang kami inginkan. Selain itu, iterator biasanya tidak lebih cepat (mungkin lebih lambat) daripada menggunakan op [, jika itu yang menjadi perhatian Anda.
7
Algoritme sebaiknya dipilih daripada untuk loop, karena tidak terlalu rentan terhadap kesalahan dan memiliki peluang pengoptimalan yang lebih baik. Ada artikel tentang itu di suatu tempat ... ini dia: drdobbs.com/184401446
AshleysBrain
5
@Ashley Sampai saya melihat beberapa statistik obyektif tentang "kurang rawan kesalahan" saya melihat tidak perlu mempercayainya. Dan Meyers dalam artikel tersebut tampaknya berbicara tentang loop yang menggunakan iterator - saya berbicara tentang efisiensi loop yang JANGAN menggunakan iterator - tolok ukur saya sendiri cenderung menunjukkan bahwa ini sedikit lebih cepat saat dioptimalkan - tentu saja tidak lebih lambat.
1
Inilah saya, saya juga menemukan solusi Anda jauh lebih baik.
peterh - Pulihkan Monica
5

Masalahnya di sini tampaknya bukan pada resolusi yang berlebihan tetapi pada kenyataannya pengurangan parameter template . Meskipun jawaban yang sangat baik dari @In silico akan menyelesaikan masalah kelebihan beban yang ambigu secara umum, tampaknya perbaikan terbaik saat berhadapan dengan std::for_each(atau serupa) adalah dengan secara eksplisit menentukan parameter templatnya :

// Simplified to use free functions instead of class members.

#include <algorithm>
#include <iostream>
#include <string>

void f( char c )
{
  std::cout << c << std::endl;
}

void f( int i )
{
  std::cout << i << std::endl;
}

void scan( std::string const& s )
{
  // The problem:
  //   error C2914: 'std::for_each' : cannot deduce template argument as function argument is ambiguous
  // std::for_each( s.begin(), s.end(), f );

  // Excellent solution from @In silico (see other answer):
  //   Declare a pointer of the desired type; overload resolution occurs at time of assignment
  void (*fpc)(char) = f;
  std::for_each( s.begin(), s.end(), fpc );
  void (*fpi)(int)  = f;
  std::for_each( s.begin(), s.end(), fpi );

  // Explicit specification (first attempt):
  //   Specify template parameters to std::for_each
  std::for_each< std::string::const_iterator, void(*)(char) >( s.begin(), s.end(), f );
  std::for_each< std::string::const_iterator, void(*)(int)  >( s.begin(), s.end(), f );

  // Explicit specification (improved):
  //   Let the first template parameter be derived; specify only the function type
  std::for_each< decltype( s.begin() ), void(*)(char) >( s.begin(), s.end(), f );
  std::for_each< decltype( s.begin() ), void(*)(int)  >( s.begin(), s.end(), f );
}

void main()
{
  scan( "Test" );
}
aldo
sumber
4

Jika Anda tidak keberatan menggunakan C ++ 11, berikut adalah penolong pintar yang mirip dengan (tapi tidak seburuk) cast statis:

template<class... Args, class T, class R>
auto resolve(R (T::*m)(Args...)) -> decltype(m)
{ return m; }

template<class T, class R>
auto resolve(R (T::*m)(void)) -> decltype(m)
{ return m; }

(Berfungsi untuk fungsi anggota; harus jelas cara memodifikasinya agar berfungsi untuk fungsi yang berdiri sendiri, dan Anda harus dapat menyediakan kedua versi dan kompilator akan memilih yang tepat untuk Anda.)

Dengan terima kasih kepada Miro Knejp karena telah menyarankan: lihat juga https://groups.google.com/a/isocpp.org/d/msg/std-discussion/rLVGeGUXsK0/IGj9dKmSyx4J .

Matthew
sumber
Masalah OP tidak dapat meneruskan nama yang kelebihan beban ke dalam templat fungsi, dan solusi Anda melibatkan pemberian nama yang kelebihan beban ke dalam templat fungsi? Ini masalah yang persis sama.
Barry
1
@Bar Bukan masalah yang sama. Pengurangan argumen template berhasil dalam kasus ini. Ini berfungsi (dengan beberapa perubahan kecil).
Oktalis
@Oktalist Karena Anda menyediakan R, itu tidak disimpulkan. Itu juga tidak disebutkan dalam jawaban ini.
Barry
1
@ Barry Saya tidak menyediakan R, saya menyediakan Args. Rdan Tdisimpulkan. Memang benar jawabannya bisa diperbaiki. (Tidak ada Tdalam contoh saya, karena ini bukan pointer-ke-anggota, karena itu tidak akan berhasil std::for_each.)
Oktalist