2016-05-29 11 views
9

Dire che ho questa funzione:come acquisire un pacchetto di parametri inoltrando o spostando?

template <typename ...A> 
void test(A&& ...a) 
{ 
    [=]() 
    { 
    }; 
} 

è il parametro pacchetto sta per essere trasmessa in lambda o semplicemente copiato per valore? Sono preoccupato, dal momento che dovrei fare un esplicito move() o forward() di solito, come a... sono lvalue. È necessario un intermediario di tuple per inoltrarli/spostarli? In tal caso, esiste un modo semplice per decomprimere la tupla in un pacchetto di parametri, senza utilizzare il trucco degli indici?

+0

Che ne dici di catturarlo per riferimento e quindi utilizzare in avanti all'interno del corpo lambda? Catturare per valore non lo farà per te. – Arunmu

+0

Inoltre, se si potesse scrivere uno pseudo codice su cosa esattamente si vuole fare, sarebbe utile. – Arunmu

risposta

6

Una delle poche cose utili rimanenti che è possibile eseguire con std::bind. La cattura è eseguita da bind ei valori acquisiti vengono passati come argomenti a una cattura-less lambda generico:

template <typename... A> 
auto test(A&&... a) 
{ 
    auto f = [](auto&&... a) 
    { 
     // use a... 
    }; 
    return std::bind(f, std::forward<A>(a)...); 
} 

Live demo

Le opere di cui sopra con Clang, ma questo GCC sembra avere un problema con un spurio volatile qualificatore.

Possiamo farlo senza bind catturando un tuple in un secondo lambda che chiama std::apply (C++ 17) per decomprimere la tupla nella lista dei parametri del primo lambda:

template <typename... A> 
auto test(A&&... a) 
{ 
    auto f = [](auto&&... a) 
    { 
     // use a... 
    }; 
    return [f, tup = std::make_tuple(std::forward<A>(a)...)]() mutable { std::apply(f, tup); }; 
} 

Live demo

Funziona con Clang e GCC; apply è implementato con il trucco degli indici che volevi evitare, ma non sei esposto ad esso. Il mutable significa che il secondo operatore di chiamata di lambda è non-const, quindi gli elementi di tupla non finiscono per ottenere una qualifica const.

7

Un modo sarebbe quello di scrivere un funtore in senso Haskell. Beh, un variardico, che non è molto Haskell.

Scrivere una funzione della firma (Ts...)->(((Ts...)->X) -> X). Cioè una funzione che prende un pacchetto e restituisce una funzione. La funzione restituita può assumere una funzione tenendo quel pacchetto e valutarlo.

template<class...Ts> 
auto make_functor(Ts&&...ts); // TODO 

Una volta ottenuto ciò, possiamo risolvere facilmente il problema.

template<class ...A> 
auto test(A&& ...a) { 
    return [unpack_a=make_functor(std::forward<A>(a)...)]() mutable 
    { 
    return unpack_a([&](auto&&...a){ 
     // here you have access to a... 
     return sizeof...(a); 
    }); 
    }; 
} 

test prende un pacco, e restituisce una funzione che restituisce la dimensione di tale pacchetto (beh, fa qualsiasi cosa con il pack).

make_functor non è semplice: in pratica, scriviamo un lambda manuale, memorizzando gli arg in una tupla e decomprimendo il trucco dell'indice in un operatore().

In effetti, facciamo il pacchetto di archiviazione e disimballaggio una volta in una classe pseudo-lambda manuale, quindi riusciamo a riutilizzarlo in seguito.

A pensarci bene, potrebbe essere meglio scrivere una domanda ritardata che richiede una tupla, la memorizza, quindi usa std::apply in seguito.

template<class...Ts> 
auto delayed_apply(std::tuple<Ts...> tup){ 
    return [tup=std::move(tup)](auto&&f)->decltype(auto) mutable{ 
    return std::experimental::apply(decltype(f)(f), std::move(tup)); 
    }; 
} 

che consente di non perdere il valore/valore dei parametri!

template<class ...A> 
auto test(A&& ...a) { 
    return [unpack_a=delayed_apply(std::forward_as_tuple(std::forward<A>(a)...))]() mutable 
    { 
    return unpack_a([&](auto&&...a){ 
     // here you have access to a... 
     return sizeof...(a); 
    }); 
    }; 
} 

questo richiede std::experimental::apply.

Se si vuole negozio rvalues ​​e lasciare lvalue come riferimenti:

unpack_a=delayed_apply(std::tuple<A...>(std::forward<A>(a)...)) 

Se si desidera memorizzare entrambi L e R valori:

unpack_a=delayed_apply(std::make_tuple(std::forward<A>(a)...)) 

come si può vedere, questo approccio dà molto controllo

Se è necessario un std::experimental::apply, ci sono implementazioni di riferimento: meglio di quelle che scrivo su uno smartphone.

Si noti che make_functor può essere scritto in termini di delayed_apply, ma il contrario è ... non è vero.

In caso di confusione, unpack_a prende una lambda e decomprime la tupla utilizzata per creare unpack_a.Fondamentalmente noi immagazziniamo un oggetto che è l'intero pacchetto, quindi lo scompattiamo quando ne abbiamo bisogno all'interno del corpo del lambda.

Un tempo più lungo delayed_apply che gestisce i sovraccarichi const e non-const e forse anche rvalue può essere richiesto se si desidera che la decompressione funzioni "più volte" e "solo una volta" altre volte. Dovrà restituire una classe, non una lambda. Fastidioso. Ho fatto funzionare il codice di esempio, penso, non ancora compilando.

Fortunatamente questo tipo di cose è scrivere una volta, usare molte.

+0

L'inoltro è necessario all'interno? 'std :: forward_as_tuple (std :: forward (a) ...)' – coyotte508

+0

@coy dipende dal caso d'uso. A volte sì, a volte no. – Yakk

2

perché non sarebbe passato per valore? l'inoltro è solo alla funzione superiore.

diciamo che si passa un int, std::string& e float&& per esempio, in modo la funzione sarà simile

void test(int,string&,float&&) 
{ 
    [=]() 
    { 
    }; 
} 

da lì, l'anonimo lambda sarà copia il int, string& e float&& per valore. una copia di un riferimento è ancora una copia. è possibile utilizzare uno tuple per comprimere nuovamente gli argomenti e decomprimerli all'interno del lambda.

come utilizzare una tupla all'interno della lamda?

  1. uso una ricorsione come siamo soliti fare con il modello variadic
  2. trovare qualche applicazione non standard di std::apply e utilizzare il tuple come argomenti per anotehr funzione
+0

Sì, ma come decomprimere senza gli indici per 'get <>()' – user1095108

+0

Una copia di un riferimento è un valore in questo contesto e non è più un riferimento. E questa copia è generata dalla copia, non dallo spostamento, anche dai rvalues. – Yakk

+0

@Yakk sei corretto, ho rimosso quello –

2

prima cattura gli argomenti in una tupla con perfetta spedizioni:

template <typename ...A> 
void test(A&& ...a) 
{ 
    [tup= std::tuple<A...>(std::forward<A>(a)...)]() 
    { 
     //tup should contain the forwarded elements 
    }; 
} 

quindi utilizzare questo risposta: https://stackoverflow.com/a/7858971/835629 per decomprimere la tupla nelle successive chiamate di funzione.

//utils 
template<int ...> 
struct seq { }; 

template<int N, int ...S> 
struct gens : gens<N-1, N-1, S...> { }; 

template<int ...S> 
struct gens<0, S...> { 
    typedef seq<S...> type; 
}; 


template<typename F, typename T, int ...S> 
void unpackTupleToFunction_utils(F func, const T &tup, seq<S...>) { 
func(std::get<S>(tup) ...); 
} 

template<typename F, typename ...Args, int ...S> 
void unpackTupleToFunction(F func, const std::tuple<Args...> &tup) { 
unpackTupleToFunction_utils(func, tup, typename gens<sizeof...(Args)>::type()); 
} 

E infine per decomprimere i tupla all'interno del lambda al fine di richiamare una funzione con esso:

template <typename ...Args> 
void test(Args&& ...a) { 
    auto lambda = [tup= std::tuple<Args...>(std::forward<Args>(a)...)]() 
    { 
    unpackTupleToFunction(f, tup); 
    }; 

    lambda(); 
    lambda(); 
    lambda(); 
} 

PS: E 'un peccato che qualcosa di simile [a = (std::forward<Args>(a)...)](){}; non può essere compilato.

+0

Sì, sì, ma gli indici il trucco è qualcosa che sto cercando di evitare, sei completamente corretto però. – user1095108

Problemi correlati