2015-07-08 11 views
5

Quando assegno un lambda a una variabile tipizzata in modo esplicito (ad esempio quando è ricorsiva, per acquisire la funzione in se stessa), utilizzo std::function.Esiste un tipo di funzione std :: o simile per lambda con parametro auto?

considerare questo "bit contare" sciocco funzione come esempio:

std::function<int(int)> f; 
f = [&f](int x){ return x ? f(x/2)+1 : 0; }; 

Che dire del caso in cui usiamo un parametro auto per generalizzare x, introdotto in C++ 14 lambda generica?

std::function<int(???)> f; 
f = [&f](auto x){ return x ? f(x/2)+1 : 0; }; 

Ovviamente, non posso mettere auto nei parametri di tipo function.

Esiste la possibilità di definire una classe di functor genericamente sufficiente a coprire il caso esatto sopra, ma utilizzando ancora lambda per la definizione della funzione?

(Non generalizzare troppo, accetta solo un singolo parametro auto e codifica il valore restituito.) Il caso d'uso sarebbe per lo scenario come sopra: acquisizione della funzione in sé per riferimento per le chiamate ricorsive.

+0

Il problema qui, penso, è che una funzione di lamba ha bisogno di un tipo definitivo - 'auto' lascia semplicemente che la compilazione venga calcolata da sola. Ciò non significa che è possibile passare la funzione lambda a qualsiasi codice che abbia qualsiasi tipo in esso - il tipo deve essere noto al momento della compilazione della funzione lambda. –

+1

Che dire di 'auto & f = [& f] (auto x) {return x? f (x/2) +1: 0;}; '? Funziona? (Forse solo 'auto' invece di' auto & '.) Non ho a disposizione un compilatore compatibile con C++ 14, quindi non posso testarlo facilmente. – celticminstrel

+0

'std :: function' utilizza la cancellazione dei tipi. Sono abbastanza sicuro che * è fondamentalmente impossibile * scrivere un wrapper di oggetti funzione di tipo cancellato che accetta tipi arbitrari, poiché ogni insieme di tipi di argomenti deve * creare * una nuova funzione, che può essere fatta solo se il compilatore può vedere il modello di funzione * incartato *. Le soluzioni alternative includono l'uso di un insieme fisso di insiemi di tipi di parametri (utilizzare un set esistente di sovraccarichi/precompilazione di tutti i sovraccarichi prima della cancellazione del testo) o la cancellazione del tipo degli argomenti. – dyp

risposta

4

È possibile creare una lambda che si definisce passandolo a se stesso come parametro:

auto f = [](auto self, auto x) -> int { 
    return x ? self(self, x/2) + 1 : 0; 
}; 

std::cout << f(f, 10); 

È quindi possibile catturare quel lambda in un altro lambda, in modo da non dovete preoccuparvi di passarlo a stessa:

auto f2 = [&f](auto x) { 
    return f(f, x); 
}; 

std::cout << f2(10); 
+0

Anche se non rispondi alla domanda reale (volevo digitare un nome di tipo per il functor), suppongo di poterlo usare per risolvere il mio problema reale (che è troppo lungo per spiegare - ho pensato di dover dare esplicitamente il un tipo di functor, ma credo di poterlo aggirare con la soluzione nel mio concreto scenario). – leemes

3

Ecco un breve y-combinatore motore basato ricorsivo:

template<class F> 
struct recursive_t { 
    F f; 

    // note Self must be an lvalue reference. Things get 
    // strange if it is an rvalue: 
    // invoke makes recursive ADL work a touch better. 
    template<class Self, class...Args> 
    friend auto invoke(Self& self, Args&&...args) 
    -> decltype(self.f(self, std::declval<Args>()...)) 
    { 
    return self.f(self, std::forward<Args>(args)...); 
    } 
    // calculate return type using `invoke` above: 
    template<class Self, class...Args> 
    using R = decltype(invoke(std::declval<Self>(), std::declval<Args>()...)); 

    template<class...Args> 
    R<recursive_t&, Args...> operator()(Args&&...args) 
    { 
    return invoke(*this, std::forward<Args>(args)...); 
    } 
    template<class...Args> 
    R<recursive_t const&, Args...> operator()(Args&&...args)const 
    { 
    return invoke(*this, std::forward<Args>(args)...); 
    } 
}; 

template<class F> 
recursive_t< std::decay_t<F> > recurse(F&& f) 
{ 
    return {std::forward<F>(f)}; 
} 

Ora si può fare:

auto f = recurse([](auto&& f, auto x){ return x ? f(x/2)+1 : 0; }); 

e si ottiene un lambda ricorsiva che non dispone di una cattura & (che limita il suo utilizzo per l'ambito corrente).

Catturare un std::function in base al riferimento significa che il tempo di vita del vostro lambda è l'ambito corrente e che ogni chiamata ricorsiva richiede la cancellazione del tipo (bloccando qualsiasi ottimizzazione possibile, come la ricorsione in coda, sulla chiamata ricorsiva). Lo stesso vale per altre soluzioni simili.

L'uso di recursive_t è necessario anziché utilizzare una lambda, perché un lambda non può nominarsi al suo interno.

Live example.

Una versione basata su lambda è in qualche modo più semplice nell'implementazione. Si noti che avresti bisogno di una funzione di tipo diverso per lambda mutabili e immutabili:

template<class F> 
auto recurse(F&& f) { 
    return [f=std::forward<F>(f)](auto&&...args){ 
    return f(f, decltype(args)(args)...); 
    }; 
}; 

I recursive_t opere come:

auto fib = recurse([](auto&& fib, int x){ if (x<2) return 1; return fib(x-1)+fib(x-2); }); 

la versione lambda funziona come:

auto fib = recurse([](auto&& self, int x){ if (x<2) return 1; return self(self, x-1)+self(self,x-2); }); 

che ho , personalmente, trovare più imbarazzante.

È anche più difficile descrivere il tipo di recurse. Per la versione recursive_t, recurse è di tipo:

((A->B)->A->B)->(A->B) 

che è scomodo, ma un tipo finito.

La versione lambda è più complicata. Il tipo dell'argomento funzione recursive è di tipo:

F:= F->A->B 

quali è fastidiosamente infinita, e quindi recurse è di tipo

F->A->(A->B) 

che eredita l'infinito.

In ogni caso, il valore di ritorno recurse può quindi essere memorizzato in un banale std::function o non memorizzato in alcun contenitore cancellato dal tipo.

Problemi correlati