2013-01-07 3 views
17

Ho un modello di funzione di lavoro che chiama un lambda.Come utilizzare l'inoltro perfetto variadic in una lambda?

Vorrei generalizzare questo modello di funzione per prendere argomenti variadici e inoltrarli perfettamente nel lambda, ma ho difficoltà a compilare questo codice.

Sto utilizzando gcc 4.7.2.

UPDATE

Utilizzando il suggerimento di R. Martinho Fernandes, ho guardato l'errore in Bugzilla - it does look like a bug that's been around for a while. Se qualcuno sa di una soluzione alternativa (ne sto cercando uno ora), si prega di inviare una risposta.

ERRORI

junk.cpp: In lambda function: 
junk.cpp:32:68: error: parameter packs not expanded with ‘...’: 
junk.cpp:32:68: note:   ‘args’ 
junk.cpp: In instantiation of ‘std::pair<std::basic_string<char>, typename T::Lambda> MP(const string&, M, Args&& ...) [with T = Integer; M = int (Integer::*)()const; Args = {}; typename T::Lambda = std::function<std::function<int()>(const Integer&)>; std::string = std::basic_string<char>]’: 
junk.cpp:47:42: required from here 
junk.cpp:34:2: error: using invalid field ‘MP(const string&, M, Args&& ...)::<lambda(const T&)>::__args’ 
make: *** [junk] Error 1 

CODICE

#include <functional> 
#include <iostream> 
#include <map> 

struct Integer 
{ 
    typedef std::function<int()>       Function; 
    typedef std::function<Function(Integer const& inst)> Lambda; 

    virtual int getInt() const = 0; 
}; 

struct IntImpl : public Integer 
{ 
    virtual int getInt() const { return 42; } 
}; 

typedef std::function<int()>        IntFunction; 
typedef std::function<IntFunction(Integer const& inst)> IntLambda; 

#define WONT_COMPILE 

template<typename T,typename M,typename... Args> 
std::pair<std::string,typename T::Lambda> 
MP(std::string const& str, M method, Args&&... args) 
{ 
#ifdef WONT_COMPILE 
    return std::make_pair(str, 
     [=](T const& inst) 
     { 
      // COMPILE ERROR (Line 32) on next line 
      return std::bind(method, std::cref(inst), std::forward<Args>(args)...); 
     } 
    ); 
#else 
    return std::make_pair(str, 
     [method](T const& inst) 
     { 
      return std::bind(method, std::cref(inst)); 
     } 
    ); 
#endif 
} 

std::map<std::string,IntLambda> const g_intTbl = 
{ 
    MP<Integer>("getInt", &Integer::getInt) 
}; 

int 
main(int argv, char* argc[]) 
{ 
    IntImpl x; 
    std::cerr << g_intTbl.find("getInt")->second(x)() << std::endl; 
} 
+1

Alcuni di questi errori sembrano verificarsi perché 'std :: avanti (args ...)' dovrebbe essere 'std :: avanti (args) ...' –

+0

@AndreiTita +1 ty - che aiuta - I sto ancora ricevendo degli errori - OP è stato aggiornato – kfmfe04

+1

Beh, non so se ti è permesso espandere i pacchetti variadic nelle catture lambda (lo verificherò tra un momento), ma una soluzione alternativa sarebbe usare una cattura completa come '[=]'. –

risposta

7

Questo sembra essere un guasto del compilatore (si prega di segnalarlo se non lo è già). Lo standard dice:

Un'espansione pacchetto costituito da un pattern e un'ellissi, il istanza di che produce zero o più istanze del pattern in un elenco (descritto di seguito). La forma del motivo dipende da nel contesto in cui si verifica l'espansione. espansioni Pack possono verificarsi nei seguenti contesti:

- [...]
- In un cattura-list (5.1.2); il modello è una cattura.
- [...]

Questo rende il codice corretto.

Fino a quando non si ottiene un compilatore in grado di gestirlo, sarà necessario acquisire tutto come soluzione alternativa, con [=].

+0

Heh stavo proprio per scrivere lo stesso (ma hai citazioni standard più belle). In particolare è un bug di gcc - MSVC (novembre CTP) e Clang 3.2 accettano espansioni di pacchetti di argomenti in lambda capture. –

+0

+1 per far luce sul problema: se qualcuno conosce una soluzione alternativa a gcc, per favore postare i tuoi risultati - – kfmfe04

11

Se qualcuno sa di una soluzione (sto scavare intorno per un ora), si prega di inviare una risposta

Ho incontrato lo stesso identico problema e ha trovato una soluzione. È una specie di risposta tardiva, spero che tu abbia trovato una soluzione nel frattempo, ma qui è comunque (potrebbe per lo meno essere utile agli altri).

L'idea è quella di modificare i parametri del lambda modo che accetti anche gli stessi argomenti variadic come la funzione esterna (es. [](int) {} diventa [](int, Args&&... args) {}) e bind lambda agli argomenti variadic della funzione esterna. Fatto ciò, non c'è più problema nell'inoltro degli argomenti variadici all'interno del lambda.

Per riassumere:

template<typename... Args> 
std::function<void (int)> foo(Args&&... args) { 
    return [&](int bar) { 
        // COMPILER BUG: doesn't work with GCC 4.7 despite the capture 
        doSomething(bar, std::forward<Args>(args)...); 
       }; 
} 

template<typename... Args> 
std::function<void (int)> foo(Args&&... args) { 
    return std::bind([](int bar, Args&&... args) { 
          // now this works with GCC 4.7 
          doSomething(bar, std::forward<Args>(args)...); 
         }, 
        std::placeholders::_1, std::forward<Args>(args)...); 
} 

sicuro che questo è un brutto hack, ma almeno si può ancora ottenere la funzionalità desiderata, anche quando sei bloccato con un compilatore buggy.

+0

+1 ty - Proverò questo – kfmfe04

+0

Funziona come scritto? Penso che sia necessario modificare la firma del valore di ritorno per tenere conto del fatto che la funzione std :: sta prendendo 'int' e' Args && ... 'come parametri. –

+0

@Sean No, il codice che ho postato funziona così com'è. Non è necessario cambiare il tipo di valore di ritorno di 'foo' perché la parte' Args && ... 'è * vincolata * prima di restituire il functor. Questo è l'intero punto di 'bind' in effetti: applicazione parziale degli argomenti del funtore associato. – syam