2012-07-24 13 views
17

Sto giocando con le funzionalità funzionali di C++ 11. Una cosa che trovo strano è che il tipo di una funzione lambda NON è in realtà una funzione <>. Inoltre, le lambda non sembrano suonare molto bene con il meccanismo di inferenza del tipo.perché le funzioni lambda in C++ 11 non hanno tipi di funzione <>?

In allegato è un piccolo esempio in cui ho provato a capovolgere i due argomenti di una funzione per l'aggiunta di due numeri interi. (Il compilatore che ho usato era gcc 4.6.2 in MinGW.) Nell'esempio, il tipo per addInt_f è stato definito in modo esplicito utilizzando la funzione <> mentre addInt_l è un lambda il cui tipo è basato su tipo con auto.

Quando ho compilato il codice, la funzione flip può accettare la versione esplicitamente tipo definito di addInt ma non la versione lambda, dando un errore dicendo che, testCppBind.cpp:15:27: error: no matching function for call to 'flip(<lambda(int, int)>&)'

Le prossime righe mostrano che la versione lambda (così come una versione "grezza") può essere accettata se è espressamente trasmesso alla funzione appropriata <> type.

Quindi le mie domande sono:

  1. Perché è che una funzione lambda non hai tipo function<> in primo luogo? Nel piccolo esempio, perchénon ha function<int (int,int)> come tipo invece di avere un diverso, tipo lambda? Dal punto di vista della programmazione funzionale, qual è la differenza tra una funzione/oggetto funzionale e una lambda?

  2. Se c'è una ragione fondamentale per cui questi due devono essere diversi. Ho sentito che i lambda possono essere convertiti in function<> ma sono diversi. Si tratta di un problema di progettazione/difetto di C++ 11, un problema di implementazione o c'è un vantaggio nel distinguere i due come sono? Sembra che la firma del tipo di addInt_l abbia fornito sufficienti informazioni sul parametro e sui tipi di ritorno della funzione.

  3. C'è un modo per scrivere la lambda in modo da evitare il suddetto tipo di casting esplicito?

Grazie in anticipo.

//-- testCppBind.cpp -- 
    #include <functional> 
    using namespace std; 
    using namespace std::placeholders; 

    template <typename T1,typename T2, typename T3> 
    function<T3 (T2, T1)> flip(function<T3 (T1, T2)> f) { return bind(f,_2,_1);} 

    function<int (int,int)> addInt_f = [](int a,int b) -> int { return a + b;}; 
    auto addInt_l = [](int a,int b) -> int { return a + b;}; 

    int addInt0(int a, int b) { return a+b;} 

    int main() { 
     auto ff = flip(addInt_f); //ok 
     auto ff1 = flip(addInt_l); //not ok 
     auto ff2 = flip((function<int (int,int)>)addInt_l); //ok 
     auto ff3 = flip((function<int (int,int)>)addInt0); //ok 

     return 0; 
    } 

+4

Non dovresti utilizzare argomenti 'std :: function', principalmente perché inibisce la deduzione del tipo (che è il tuo problema qui). –

+1

Correlato: [C++ 11 non deduce il tipo quando sono coinvolte le funzioni std :: function o lambda] (http://stackoverflow.com/q/9998402/487781) – hardmath

+1

Lambdas vengono trasformati in anonimi Functional (o funzioni se non catturare un ambiente). Trasformarli in std :: function avrebbe introdotto un forte accoppiamento tra il linguaggio e la libreria e sarebbe quindi una pessima idea. – MFH

risposta

39

std::function è uno strumento utile per deposito qualsiasi oggetto richiamabile indipendentemente dal suo tipo. Per fare questo ha bisogno di impiegare qualche tecnica di cancellazione del tipo, e questo comporta un sovraccarico.

Qualsiasi callable può essere convertito implicitamente in un std::function, ed è per questo che di solito funziona senza problemi.

lo ripeto per assicurarsi che diventa chiaro: std::function non è qualcosa solo per lambda o puntatori a funzione: è per qualsiasi tipo di callable. Questo include cose come struct some_callable { void operator()() {} };, per esempio. Questo è un semplice, ma potrebbe essere qualcosa di simile a questo, invece:

struct some_polymorphic_callable { 
    template <typename T> 
    void operator()(T); 
}; 

Un lambda è solo l'ennesimo oggetto callable, simile a istanze del some_callable oggetto sopra. Può essere memorizzato in un std::function perché è chiamabile, ma non ha il sovraccarico di cancellazione del tipo di std::function.

E il comitato progetta di rendere lambdas polimorfici in futuro, cioè lambda che assomigliano a some_polymorphic_callable sopra. Quale tipo std::function sarebbe un simile lambda?


Ora ... Detrazione del parametro modello o conversioni implicite. Sceglierne uno. Questa è una regola dei modelli C++.

Per passare un lambda come argomento std::function, è necessario convertirlo implicitamente. L'utilizzo di un argomento std::function significa che stai scegliendo conversioni implicite sulla deduzione di tipo. Ma il tuo modello di funzione richiede che la firma sia dedotta o fornita esplicitamente.

La soluzione? Non limitare i chiamanti a std::function. Accetta qualsiasi tipo di richiamabile.

template <typename Fun> 
auto flip(Fun&& f) -> decltype(std::bind(std::forward<Fun>(f),_2,_1)) 
{ return std::bind(std::forward<Fun>(f),_2,_1); } 

Si può ora pensare perché abbiamo bisogno std::function allora. std::function fornisce la cancellazione del tipo per callables con una firma conosciuta. In sostanza, è utile archiviare le callables cancellate dal testo e scrivere le interfacce virtual.

+0

: Grazie per la tua chiara spiegazione. IMHO, il fatto che la funzione std :: possa essere utilizzata solo per la memorizzazione è piuttosto deludente. Se capisco la tua risposta correttamente, questo è per evitare un sovraccarico? Quindi, lo stesso vale per i puntatori intelligenti? Inoltre, nell'esempio 'some_polymorphic_callable', non vedo perché il tipo di funzione <> non possa esprimere il tipo per il lambda basato su modello poiché la funzione <> può già avere parametri di template. Se l'unico problema è l'overhead, qualcuno ha considerato che una funzione leggera <> può essere utilizzata nei tipi di parametri? Renderebbe STL molto più utilizzabile. – tinlyx

+0

Inoltre, nell'esempio 'decltype', se il corpo della funzione è composto da 25 linee di codice anziché da un solo elemento, è necessario includere 25 righe in decltype? Inoltre, l'uso di '' sembra quasi come void * o una macro per me. L'uso errato di 'Fun' può essere rilevato solo quando si verifica un errore di compilazione. Per confronto, un tipo di parametro <> del parametro (se utilizzabile) indica chiaramente alla persona e al computer la firma del tipo. Devo ammettere che ho una conoscenza molto limitata di "cancellazione di tipi" e simili. Grazie per avermi detto le regole. Mi sto solo chiedendo perché debba essere così. – tinlyx

+0

@TingL Il tipo per il lambda polimorfo non può essere espresso perché 'function <> 'richiede un argomento * one * signature, mentre il lambda polimorfico avrà * un numero illimitato di firme diverse * (ecco perché si chiama polimorfico: ha molte forme). Il problema generale non si applica esattamente allo stesso modo per i puntatori intelligenti. 'unique_ptr' ha un overhead veramente * zero * (era uno degli obiettivi di progettazione). 'shared_ptr' può avere un sovraccarico se non stai eseguendo un programma multithread, ma i programmi multithread sono un caso in cui è più probabile che vengano utilizzati. –

5
  1. Perché function<> impiega type erasure. Ciò consente di memorizzare diversi tipi di funzioni diverse in un function<>, ma incorre in una piccola penalità di runtime. Il tipo di cancellazione nasconde il tipo effettivo (il tuo lambda specifico) dietro un'interfaccia di funzione virtuale.
  2. C'è un vantaggio: uno degli assiomi del design C++ è di non aggiungere mai un sovraccarico a meno che non sia realmente necessario. Utilizzando questa impostazione, non si ha alcun sovraccarico quando si utilizza l'inferenza di tipo (utilizzare auto o passare come parametro del modello), ma si ha ancora tutta la flessibilità per interfacciarsi con il codice non modello tramite function<>. Si noti inoltre che function<> non è un costrutto linguistico, ma un componente della libreria standard che può essere implementato utilizzando funzionalità linguistiche semplici.
  3. No, ma è possibile scrivere la funzione semplicemente prendendo il tipo di funzione (costrutto del linguaggio) anziché le specifiche dello function<> (costrutto di libreria).Ovviamente, ciò rende molto più difficile scrivere effettivamente il tipo di ritorno, dal momento che non fornisce direttamente i tipi di parametri. Tuttavia, utilizzando alcune meta-programmazione a la Boost.FunctionTypes è possibile ricavarle dalla funzione che si passa. Ci sono alcuni casi in cui ciò non è possibile, ad esempio con i funtori che hanno un modello operator().
Problemi correlati