2016-06-09 4 views
10

Si consideri il seguente codice:Copia di una funzione lambda con i parametri di default per una variabile

#include <iostream> 
#include <functional> 
using namespace std; 

int main() { 
    auto f = [](int a = 3) {cout << a << endl; }; 
    f(2); // OK 
    f(); // OK 

    auto g = f; 
    g(2); // OK 
    g(); // OK 

    function<void(int)> h = f; 
    h(2); // OK 
    h(); // Error! How to make this work? 

    return 0; 
} 

Come posso dichiarare h a comportarsi lo stesso f e g?

+0

'auto h = f;'? – MikeCAT

+0

Non penso che 'std :: function' abbia un supporto di parametri predefinito. – chris

+0

@MikeCAT So che :) Qual è il modo corretto se voglio specificare completamente il tipo, se ce n'è? –

risposta

12

std::function ha una firma fissa. Questa era una scelta di design, non un requisito difficile. Scrivi pseudo std::function che supporta più firme non è difficile:

template<class...Sigs> 
struct functions; 

template<> 
struct functions<> { 
    functions()=default; 
    functions(functions const&)=default; 
    functions(functions&&)=default; 
    functions& operator=(functions const&)=default; 
    functions& operator=(functions&&)=default; 
private: 
    struct never_t {private:never_t(){};}; 
public: 
    void operator()(never_t)const =delete; 

    template<class F, 
    std::enable_if_t<!std::is_same<std::decay_t<F>, functions>{}, int>* =nullptr 
    > 
    functions(F&&) {} 
}; 

template<class S0, class...Sigs> 
struct functions<S0, Sigs...>: 
    std::function<S0>, 
    functions<Sigs...> 
{ 
    functions()=default; 
    functions(functions const&)=default; 
    functions(functions&&)=default; 
    functions& operator=(functions const&)=default; 
    functions& operator=(functions&&)=default; 
    using std::function<S0>::operator(); 
    using functions<Sigs...>::operator(); 
    template<class F, 
    std::enable_if_t<!std::is_same<std::decay_t<F>, functions>{}, int>* =nullptr 
    > 
    functions(F&& f): 
    std::function<S0>(f), 
    functions<Sigs...>(std::forward<F>(f)) 
    {} 
}; 

uso:

auto f = [](int a = 3) {std::cout << a << std::endl; }; 

functions<void(int), void()> fs = f; 
fs(); 
fs(3); 

Live example.

Ciò creerà una copia separata della lambda per sovraccarico. È persino possibile avere diversi lambda per sovraccarichi diversi con getto attento.

È possibile scriverne uno che non lo fa, ma in pratica richiederebbe reimplementare std::function con uno stato interno più elaborato.

Una versione più avanzata di quanto sopra eviterebbe l'utilizzo dell'ereditarietà lineare, poiché ciò comporta il numero O (n^2) e la profondità del modello ricorsiva O (n) sul numero di firme. Un'ereditarietà di alberi binari bilanciati scende a O (n lg n) codice generato e O (lg n) profondità.

La versione forza industriale sarebbe conservare il passato in lambda volta, utilizzare l'ottimizzazione oggetto piccolo, avere una pseudo-vtable manuale che utilizza una strategia di successione binaria di inviare la chiamata di funzione, e conservare inviato a funzionare puntatori in detta pseudo vtable. Ci vorrebbe spazio O (# firme) * sizeof (puntatore di funzione) in una base per classe (non per istanza), e utilizzare circa il sovraccarico di ogni istanza come farebbe std::function.

Ma questo è un po 'troppo per un post SO, no?

start of it

+3

hai appena scritto queste risposte in 10 minuti o lo hai copiato dai frammenti di C++? –

+1

@BryanChen lo ha scritto. Compilato per la prima volta, che è stata la parte sorprendente. Se avessi usato uno snippet pre-scritto, spero di evitare quell'ereditarietà lineare inefficiente!Ho già fatto cose del genere prima, con poly-lambdas che non usano la cancellazione dei tipi, ma non penso di aver fatto un poly-'std :: function'. – Yakk

+0

@Yakk Soluzione molto interessante! –

2

Mi piace la bella soluzione proposta da @Yakk.

Detto questo, si può fare qualcosa di simile, evitando le std::function s e utilizzando una funzione di proxy per il lambda, modelli variadic e std::forward, come segue:

#include<functional> 
#include<utility> 
#include<iostream> 

template<typename... Args> 
void f(Args&&... args) { 
    auto g = [](int a = 3) { std::cout << a << std::endl; }; 
    g(std::forward<Args>(args)...); 
}; 

int main() { 
    f(2); 
    f(); 
} 

Mettere le idee di cui sopra in un funtore e avrai un oggetto simile alla funzione che fa quasi la stessa cosa.

Ha il problema che i parametri non sono strettamente verificati.
In ogni caso, fallirà in fase di compilazione se usato in modo improprio.