Come posso specificare un puntatore a una funzione sovraccaricata?


137

Voglio passare una funzione sovraccarica std::for_each()all'algoritmo. Per esempio,

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

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

Mi aspetto che il compilatore si risolva in f()base al tipo di iteratore. Apparentemente (GCC 4.1.2) non lo fa. Quindi, come posso specificare quale f()voglio?


Risposte:


137

È possibile utilizzare static_cast<>()per specificare quale futilizzare in base alla firma della funzione implicita nel tipo di puntatore a funzione:

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

Oppure puoi anche fare questo:

// 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

Se fè una funzione membro, è necessario utilizzare mem_fun, o per il proprio caso, utilizzare la soluzione presentata in questo articolo del Dr. Dobb .


1
Grazie! Tuttavia, ho ancora un problema, probabilmente a causa del fatto che f()è un membro di una classe (vedi l'esempio modificato sopra)
davka

9
@the_drow: il secondo metodo è in realtà molto più sicuro, se uno dei sovraccarichi scompare il primo metodo fornisce silenziosamente un comportamento indefinito, mentre il secondo rileva il problema al momento della compilazione.
Ben Voigt,

3
@BenVoigt Hmm, ho provato questo su vs2010 e non sono riuscito a trovare un caso in cui static_cast non potesse rilevare il problema al momento della compilazione. Ha dato un C2440 con "Nessuna delle funzioni con questo nome nell'ambito corrisponde al tipo di destinazione". Puoi chiarire?
Nathan Monteleone,

5
@Nathan: è possibile che stavo pensando reinterpret_cast. Molto spesso vedo i cast in stile C usati per questo. La mia regola è solo che i cast sui puntatori a funzioni sono pericolosi e non necessari (come mostra il secondo frammento di codice, esiste una conversione implicita).
Ben Voigt,

3
Per le funzioni membro:std::for_each(s.begin(), s.end(), static_cast<void (A::*)(char)>(&A::f));
sam-w,

29

Lambdas in soccorso! (nota: C ++ 11 richiesto)

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

O usando decltype per il parametro lambda:

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

Con lambda polimorfi (C ++ 14):

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

O disambiguare rimuovendo il sovraccarico (funziona solo con funzioni gratuite):

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

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

Evviva gli lambda! In effetti, un'ottima soluzione al problema della risoluzione del sovraccarico. (Ho pensato anche a questo, ma ho deciso di lasciarlo fuori dalla mia risposta per non confondere le acque.)
aldo,

Più codice per lo stesso risultato. Penso che non sia quello per cui sono state fatte le lambda.
Tomáš Zato - Ripristina Monica il

@ TomášZato La differenza è che questa risposta funziona e quella accettata no (per l'esempio pubblicato da OP - devi anche usare mem_fne bindche, a proposito, sono anche C ++ 11). Inoltre, se vogliamo diventare davvero pedanti [&](char a){ return f(a); }sono 28 caratteri e static_cast<void (A::*)(char)>(&f)35 caratteri.
Milleniumbug,

1
@ TomášZato Lì vai coliru.stacked-crooked.com/a/1faad53c4de6c233 non sei sicuro di come renderlo più chiaro
milleniumbug

18

Perché non funziona?

Mi aspetto che il compilatore si risolva in f()base al tipo di iteratore. Apparentemente, (gcc 4.1.2) non lo fa.

Sarebbe bello se fosse così! Tuttavia, for_eachè un modello di funzione, dichiarato come:

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

La detrazione del modello deve selezionare un tipo per UnaryFunctionnel punto della chiamata. Ma fnon ha un tipo specifico - è una funzione sovraccarica, ce ne sono molti fciascuno con tipi diversi. Non esiste un modo attuale per for_eachaiutare il processo di detrazione del modello dichiarando ciò fche desidera, quindi la detrazione del modello semplicemente fallisce. Affinché la deduzione del modello abbia esito positivo, è necessario svolgere più lavoro sul sito di chiamata.

Soluzione generica per risolverlo

Saltando qui qualche anno e C ++ 14 più tardi. Invece di usare un static_cast(che consentirebbe alla deduzione del modello di avere successo "riparando" che fvogliamo usare, ma richiede che tu faccia manualmente la risoluzione del sovraccarico per "riparare" quella corretta), vogliamo far funzionare il compilatore per noi. Vogliamo chiamare falcuni argomenti. Nel modo più generico possibile, questo è:

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

È molto da scrivere, ma questo tipo di problema si presenta fastidiosamente frequentemente, quindi possiamo semplicemente avvolgerlo in una macro (sospiro):

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

e poi basta usarlo:

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

Questo farà esattamente quello che vorresti fosse fatto dal compilatore: esegui la risoluzione del sovraccarico sul nome fstesso e fai semplicemente la cosa giusta. fFunzionerà indipendentemente dal fatto che sia una funzione libera o una funzione membro.


7

Non per rispondere alla tua domanda, ma sono l'unico che trova

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

sia più semplice che più breve for_eachdell'alternativa suggerita da in silico in questo caso?


2
probabilmente, ma è noioso :) Inoltre, se voglio usare iteratore per evitare l'operatore [], questo diventa più lungo ...
davka

3
@Davka Boring è ciò che vogliamo. Inoltre, gli iteratori in genere non sono più veloci (possono essere più lenti) rispetto all'utilizzo di op [, se questo è il tuo problema.

7
Gli algoritmi dovrebbero essere preferiti ai loop, poiché sono meno soggetti a errori e possono avere migliori opportunità di ottimizzazione. C'è un articolo su questo da qualche parte ... eccolo qui: drdobbs.com/184401446
AshleysBrain

5
@Ashley Fino a quando vedrò alcune statistiche oggettive sul "meno soggetto a errori" non vedo la necessità di crederci. E Meyers nell'articolo sembra parlare di loop che usano iteratori - sto parlando dell'efficienza dei loop che NON usano iteratori - i miei benchmark tendono a suggerire che questi sono leggermente più veloci quando ottimizzati - certamente non più lenti.

1
Anche io trovo la tua soluzione molto migliore.
Peter - Ripristina Monica il

5

Il problema qui sembra non essere la risoluzione del sovraccarico ma la deduzione dei parametri del modello . Mentre l' eccellente risposta di @In silico risolverà un ambiguo problema di sovraccarico in generale, sembra che la soluzione migliore quando si ha a che fare con std::for_each(o simili) sia specificare esplicitamente i parametri del modello :

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

4

Se non ti dispiace usare C ++ 11, ecco un aiuto intelligente che è simile (ma meno brutto) al cast statico:

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

(Funziona per le funzioni membro; dovrebbe essere ovvio come modificarlo per funzionare per le funzioni indipendenti e dovresti essere in grado di fornire entrambe le versioni e il compilatore selezionerà quello giusto per te.)

Grazie a Miro Knejp per avermi suggerito: vedi anche https://groups.google.com/a/isocpp.org/d/msg/std-discussion/rLVGeGUXsK0/IGj9dKmSyx4J .


Il problema di OP non è in grado di passare un nome sovraccarico in un modello di funzione e la tua soluzione prevede il passaggio di un nome sovraccaricato in un modello di funzione? Questo è esattamente lo stesso problema.
Barry,

1
@ Barry Non è lo stesso problema. La deduzione dell'argomento modello ha esito positivo in questo caso. Funziona (con alcune piccole modifiche).
Oktalist,

@Oktalist Perché stai fornendo R, non è dedotto. Non c'è nemmeno menzione di questo in questa risposta.
Barry,

1
@Barry non sto fornendo R, sto fornendo Args. Re Tsono dedotti. È vero che la risposta potrebbe essere migliorata. (Non c'è nessun Tesempio nel mio esempio, perché non è un puntatore a membro, perché non funzionerebbe std::for_each.)
Oktalist
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.