2015-07-07 19 views
8

In genere io uso il seguente schema l'accettazione di una lambda come argomento di una funzione (A passò-by-value classe template):Devo passare una lambda di riferimento const.

template <class Function> 
void higherOrderFunction(Function f) { 
    f(); 
} 

Questo copia (la chiusura del) l'argomento? Se è così, c'è qualcosa di sbagliato nell'accettare il lambda di riferimento const, invece?

template <class Function> 
void higherOrderFunction(const Function& f) { 
    f(); 
} 

Un semplice test sembra indicare che questo funziona bene, ma voglio sapere se ci sono delle considerazioni particolari che dovrei essere a conoscenza.

+0

Credo che questo non copia, ma spostare. Dovrebbe essere abbastanza veloce con una implementazione decente. Lambdas è progettato per essere passato per valore come questo - tutti gli algoritmi standard prendono i loro funtori di valore. –

risposta

10

Se si passa per valore, si copierà l'oggetto di chiusura (presumendo che non si definisca il lambda in linea, nel qual caso verrà spostato). Ciò potrebbe essere indesiderato se lo stato è costoso da copiare e non riuscirà a compilare se lo stato non è copiabile.

template <class Function> 
void higherOrderFunction(Function f); 

std::unique_ptr<int> p; 
auto l = [p = std::move(p)] {}; // C++14 lambda with init capture 
higherOrderFunction(l);   // doesn't compile because l is non-copyable 
           // due to unique_ptr member 
higherOrderFunction([p = std::move(p)] {}); // this still works, the closure object is moved 

Se si passa da const riferimento, allora non può passare un lambda mutable che modifica suoi membri di dati come argomento higherOrderFunction() perché un lambda mutable ha un non constoperator(), e non si può invocare che su un const oggetto.

template <class Function> 
void higherOrderFunction(Function const& f); 

int i = 0; 
higherOrderFunction([=]() mutable { i = 0; }); // will not compile 

L'opzione migliore è utilizzare un riferimento di inoltro. Quindi, higherOrderFunction può accettare sia lvalue che rvalue passati al chiamante.

template <class Function> 
void higherOrderFunction(Function&& f) { 
    std::forward<Function>(f)(); 
} 

Ciò consente la compilazione dei casi semplici e di quelli menzionati sopra. Per una discussione sul motivo per cui è necessario utilizzare std::forward, vedere this answer.

Live demo

+0

"Se passi di valore, copi l'oggetto di chiusura." Questo non è necessariamente vero, e in realtà falso nel caso più comune. Passa per valore fa una mossa su un valore di rvalore, non una copia, e il caso più comune per lambdas è quello di creare il lambda in linea, che dovrebbe risultare in un valore. La tua risposta porta anche a una domanda immediata: se i riferimenti di inoltro sono i migliori, perché l'STL usa il valore invece del riferimento di inoltro? (domanda onesta) –

+1

@ Nir Agreed, ha aggiornato la risposta per chiarire questo punto. Per quanto riguarda gli algoritmi STL che assumono il predicato in base al valore, suppongo che sia perché i riferimenti di inoltro non erano presenti in quel momento. In ogni caso, non posso pensare a un argomento per non usare un riferimento di inoltro in questo caso. – Praetorian

+3

Per essere onesti, con algoritmi 'std' che assumono funzioni per valore, le persone dovrebbero sapere che se hanno un oggetto funzione ad alto costo con una durata limitata, dovrebbero' std :: ref' it. – Yakk

1

Una copia è una copia, quindi non è possibile mutare l'originale, potrebbe avere qualche impatto sulle prestazioni quando è coinvolto un sacco di dati:

#include <iostream> 
using namespace std; 

template<typename Fn> 
void call_value(Fn f) { f(); } 
template<typename Fn> 
void call_ref(Fn & f) { f(); } 
template<typename Fn> 
void call_cref(Fn const & f) { f(); } 

struct Data { 
    Data() {} 
    Data(Data const &) { 
    cout << "copy" << endl; 
    } 
    Data(Data &&) { 
    cout << "move" << endl; 
    } 
}; 

int main(int, char **) { 
    Data data; 

    auto capref = [&data]() {}; 
    cout << "capture by value, so we get a "; 
    auto capcp = [data]() {}; 

    cout << " the lambda with a reference ... "; 
    call_value(capref); 
    cout << " could now be called and mutate .. "; 
    call_ref(capref); 
    call_cref(capref); 
    cout << " but won't, as it had to be declared mutable " << endl; 

    cout << "the lambda with an instance: "; 
    call_value(capcp); 
    cout << "but not "; 
    call_ref(capcp); 
    call_cref(capcp); 
    cout << " the reference versions " << endl; 

    bool en = false; 
    auto trigger = [en](bool enable = true) mutable { 
    if (en) { 
     cout << "fire!" << endl; 
    } 
    if (en or enable) { 
     en = true; 
    } 
    }; 
    cout << "won't shoot" << endl; 
    trigger(false); 
    call_value(trigger); 
    trigger(false); 
    call_ref(trigger); 
    cout << "and now ... "; 
    trigger(false); 
    // const ref won't work 
    return 0; 
} 

Guardalo in action.

Da non dimenticare: i lambda sono semplici zucchero sintattici per le classi callable. (Ma estremamente utile)

Problemi correlati