2016-01-15 18 views
7

Recentemente, ho riscontrato un problema strano con il passaggio di una funzione passata come parametro a un'espressione lambda. Il codice è stato compilato correttamente con clang 3.5+, ma non è riuscito con g ++ 5.3 e mi chiedo se il problema sia nello standard C++, un'estensione non standard in clang o un'interpretazione sintattica non valida in GCC.C++ 11 Funzione di passaggio come parametro lambda

Il codice di esempio è fata semplice:

template<typename Fn, typename... Args, typename T = typename std::result_of<Fn(Args...)>::type> 
    std::future<T> 
    async(Fn &&fn, Args &&... args) 
    { 
     std::shared_ptr<std::promise<T>> promise = std::make_shared<std::promise<T>>(); 
     auto lambda = [promise, fn, args...](void) 
      { promise->set_value(fn(std::move(args)...)); }; 
     send_message(std::make_shared<post_call>(lambda)); 
     return promise->get_future(); 
    }; 

GCC ha riportato il seguente:

error: variable ‘fn’ has function type 
      { promise->set_value(fn(std::move(args)...)); }; 
(...) 
error: field ‘async(Fn&&, Args&& ...) [with Fn = int (&)(int, int); Args = {int&, int&}; T = int]::<lambda()>::<fn capture>’ invalidly declared function type 
     auto lambda = [promise, fn, args...](void) 

Per fortuna, ho trovato una soluzione semplice, l'aggiunta di un oggetto std :: funzione, incapsulando il parametro della funzione :

template<typename Fn, typename... Args, typename T = typename std::result_of<Fn(Args...)>::type> 
    std::future<T> 
    async(Fn &&fn, Args &&... args) 
    { 
     std::shared_ptr<std::promise<T>> promise = std::make_shared<std::promise<T>>(); 
     std::function<T(typename std::remove_reference<Args>::type...)> floc = fn; 
     auto lambda = [promise, floc, args...](void) 
      { promise->set_value(floc(std::move(args)...)); }; 
     send_message(std::make_shared<post_call>(lambda)); 
     return promise->get_future(); 
    }; 

Anche se non capisco cosa c'era di sbagliato negli abeti t pezzo di codice, compilato con successo con clang e eseguito senza errori.


EDIT

Ho appena notato, che la mia soluzione non riesce in modo catastrofico, se uno degli argomenti doveva essere un punto di riferimento. Quindi, se avete altri suggerimenti che potrebbero funzionare con C++ 11 (cioè senza lambda specializzata [C++ 14 feature]), sarebbe davvero bello ...

+0

Che cosa succede se si passa 'p_fn auto = & fn' a lambda? –

+0

@JoelCornett sigsegv o qualcosa di simile. La stessa cosa è accaduta con il passaggio di 'fn' come riferimento. Sebbene compili, in entrambi i casi. – Marandil

risposta

5

La variabile fn ha un tipo di funzione. In particolare, ha tipo Fn = int (&)(int, int).

Un valore di tipo Fn (non un riferimento) è di tipo int(int,int).

Questo valore non può essere memorizzato.

Sono vagamente sorpreso che non si decomporrà automaticamente per te. In C++ 14, si può fare:

auto lambda = [promise, fn=fn, args...](void) 
     { promise->set_value(fn(std::move(args)...)); }; 

che dovrebbe decadere il tipo di fn (esternamente) per fn internamente. Se questo non funziona:

auto lambda = [promise, fn=std::decay_t<Fn>(fn), args...](void) 
     { promise->set_value(fn(std::move(args)...)); }; 

che decade in modo esplicito. (Il decadimento è un'operazione che rende un tipo adatto alla conservazione).

In secondo luogo, si dovrebbe aggiungere mutable:

auto lambda = [promise, fn=std::decay_t<Fn>(fn), args...](void)mutable 
     { promise->set_value(fn(std::move(args)...)); }; 

o il std::move non farà molto. (spostare un valore const non fa molto).

In terzo luogo, è possibile spostare la promessa in invece di creare un inutile condiviso PTR:

template<typename Fn, typename... Args, typename T = typename std::result_of<Fn(Args...)>::type> 
std::future<T> 
async(Fn &&fn, Args &&... args) 
{ 
    std::promise<T> promise; 
    std::future<T> ret = promise.get_future(); 
    auto lambda = 
     [promise=std::move(promise), fn=std::decay_t<Fn>(fn), args...] 
    () mutable { 
     promise.set_value(fn(std::move(args)...)); 
     }; 
    send_message(std::make_shared<post_call>(lambda)); 
    return ret; 
}; 

Questo presuppone la classe post_call in grado di gestire lambda muovere solo (se si tratta di un std::function non può).

+0

Wow, molte grazie per il consiglio, ma sfortunatamente la maggior parte di loro richiede C++ 14, e ho deciso di attenermi al C++ 11 per il progetto (entrambi i compilatori si lamentano dell'uso di estensioni C++ 14 quando usano lambda inizializzato cattura, mentre cerco di attenermi al puro C++ 11), quindi la promessa deve rimanere un puntatore (anche se immagino possa essere solo un normale puntatore, ma non sono sicuro di cosa accadrà in entrambi i casi, quando il lambda non verrà mai eseguito ...). Userò sicuramente il tuo consiglio, una volta che sto per scrivere qualcosa con C++ 1y;) – Marandil

+2

@ Marandil Move-in lambda può essere scritto manualmente come tipi di helper (è un po 'prolisso, lo ammetto). Puoi risolvere il problema del decadimento memorizzando una copia di 'fn' localmente con' auto my_fn = std :: forward (fn); ', quindi cattura' my_fn'. Nota che la mia versione C++ 14 sopra (e la tua) copia inutilmente 'args ...' nel lambda: questo è inefficiente. – Yakk

0

Il tuo primo codice compila bene sotto VS2015, quindi non in grado di riprodurre il problema, ma dal momento che si sta utilizzando riferimenti universali, vorrei provare qualcosa di simile:

template<typename Fn, typename... Args, typename T = typename std::result_of<Fn(Args...)>::type> 
std::future<T> async2(Fn &&fn, Args &&... args) 
{ 
    std::promise<T> prom; 
    auto floc = std::bind(std::forward<Fn>(fn), std::forward<Args>(args)...); 
    auto lambda = [&prom, floc](void) 
    { prom.set_value(floc()); }; 
    send_message(std::make_shared<post_call>(lambda)); 
    return prom.get_future(); 
};