2010-11-13 19 views
7

Sto provando a creare una funzione che può essere chiamata con un lambda che accetta 0, 1 o 2 argomenti. Poiché ho bisogno che il codice funzioni su g ++ 4.5 e vs2010 (che non supporta modelli variadic o conversioni lambda in puntatori di funzioni) l'unica idea che ho trovato è quella di scegliere quale implementazione chiamare in base all'arity. Di seguito è la mia ipotesi non funzionante su come dovrebbe apparire. C'è un modo per risolvere il mio codice o c'è un modo migliore per farlo in generale?C++ 0x: overloading su lambda arity

#include <iostream> 
#include <functional> 
using namespace std; 

template <class Func> struct arity; 

template <class Func> 
struct arity<Func()>{ static const int val = 0; }; 

template <class Func, class Arg1> 
struct arity<Func(Arg1)>{ static const int val = 1; }; 

template <class Func, class Arg1, class Arg2> 
struct arity<Func(Arg1,Arg2)>{ static const int val = 2; }; 

template<class F> 
void bar(F f) 
{ 
    cout << arity<F>::val << endl; 
} 

int main() 
{ 
    bar([]{cout << "test" << endl;}); 
} 
+0

Puoi mostrare un esempio di codice che chiamerebbe la funzione lambda? – Heatsink

risposta

16

una funzione lambda è un tipo di classe con un unico operatore chiamata di funzione. Si può quindi rilevare l'arità di tale operatore chiamata di funzione, prendendo il suo indirizzo e utilizzando la risoluzione di sovraccarico per scegliere quale funzione chiamare:

#include <iostream> 

template<typename F,typename R> 
void do_stuff(F& f,R (F::*mf)() const) 
{ 
    (f.*mf)(); 
} 

template<typename F,typename R,typename A1> 
void do_stuff(F& f,R (F::*mf)(A1) const) 
{ 
    (f.*mf)(99); 
} 

template<typename F,typename R,typename A1,typename A2> 
void do_stuff(F& f,R (F::*mf)(A1,A2) const) 
{ 
    (f.*mf)(42,123); 
} 

template<typename F> 
void do_stuff(F f) 
{ 
    do_stuff(f,&F::operator()); 
} 

int main() 
{ 
    do_stuff([]{std::cout<<"no args"<<std::endl;}); 
    do_stuff([](int a1){std::cout<<"1 args="<<a1<<std::endl;}); 
    do_stuff([](int a1,int a2){std::cout<<"2 args="<<a1<<","<<a2<<std::endl;}); 
} 

Attenzione però: questo non funzionerà con i tipi di funzione, o tipi di classe che avere più di un operatore di chiamata di funzione o operatori di chiamata di funzione non const.

+0

Molto interessante, tuttavia non è necessario richiamare effettivamente la funzione (nel momento in cui si vincolano i tipi di parametri) e si desidera restituire il numero di parametri da 'do_stuff' (che potrebbe essere meglio denominato' arity'). – Motti

+1

Oh sì --- Ho appena fatto la chiamata per dimostrare che potevi. –

1

Ho pensato che quanto segue avrebbe funzionato ma non lo è, lo sto postando per due motivi.

  1. per salvare la gente il tempo se avessero la stessa idea
  2. Se qualcuno sa perché questo non funziona, non sono sicuro al 100% ho capito (anche se ho i miei sospetti)

codice segue:

#include <iostream> 
#include <functional> 

template <typename Ret> 
unsigned arity(std::function<Ret()>) { return 0; } 

template <typename Ret, typename A1> 
unsigned arity(std::function<Ret(A1)>) { return 1; } 

template <typename Ret, typename A1, typename A2> 
unsigned arity(std::function<Ret(A1, A2)>) { return 2; } 

// rinse and repeat 

int main() 
{ 
    std::function<void(int)> f = [](int i) { }; // this binds fine 

    // Error: no matching function for call to 'arity(main()::<lambda(int)>)' 
    std::cout << arity([](int i) { }); 
} 
+1

std :: function sa come avvolgere lambdas, ma * tu * devi fornire direttamente la firma <...> corretta. Ad esempio: dato "modello void foo (vector );", non puoi passare a foo un elenco di inizializzazione! Il compilatore sa cosa fare con il vettore :: vector (initializer_list ), ma deve sapere prima T di arrivare così lontano. Il compilatore non può essere avviato da initializer_list , vedere un vettore e quindi selezionare magicamente T = X in modo che le cose possano coincidere. È indietro. –

-1

questo modo così:

template<typename F> 
auto call(F f) -> decltype(f(1)) 
{ 
    return f(1); 
} 

template<typename F> 
auto call(F f, void * fake = 0) -> decltype(f(2,3)) 
{ 
    return f(2,3); 
} 

template<typename F> 
auto call(F f, void * fake = 0, void * fake2 = 0) -> decltype(f(4,5,6)) 
{ 
    return f(4,5,6); 
} 

int main() 
{ 
    auto x1 = call([](int a){ return a*10; }); 
    auto x2 = call([](int a, int b){ return a*b; }); 
    auto x3 = call([](int a, int b, int c){ return a*b*c; }); 
    // x1 == 1*10 
    // x2 == 2*3 
    // x3 == 4*5*6 
} 

Funziona per tutti i tipi richiamabili (lambda, funtori, ecc)

0

Tempo di compilazione dei mezzi per ottenere l'arietà di una funzione o un oggetto funzione, tra cui quello di un lambda:

int main (int argc, char ** argv) { 
    auto f0 = []() {}; 
    auto f1 = [](int) {}; 
    auto f2 = [](int, void *) {}; 

    std::cout << Arity<decltype(f0)>::value << std::endl; // 0 
    std::cout << Arity<decltype(f1)>::value << std::endl; // 1 
    std::cout << Arity<decltype(f2)>::value << std::endl; // 2 

    std::cout << Arity<decltype(main)>::value << std::endl; // 2 
} 

template <typename Func> 
class Arity { 
private: 
    struct Any { 
     template <typename T> 
     operator T(); 
    }; 

    template <typename T> 
    struct Id { 
     typedef T type; 
    }; 

    template <size_t N> 
    struct Size { 
     enum { value = N }; 
    }; 

    template <typename F> 
    static Size<0> match (
     F f, 
     decltype(f()) * = nullptr); 

    template <typename F> 
    static Size<1> match (
     F f, 
     decltype(f(Any())) * = nullptr, 
     decltype(f(Any())) * = nullptr); 

    template <typename F> 
    static Size<2> match (
     F f, 
     decltype(f(Any(), Any())) * = nullptr, 
     decltype(f(Any(), Any())) * = nullptr, 
     decltype(f(Any(), Any())) * = nullptr); 

public: 
    enum { value = Id<decltype(match(static_cast<Func>(Any())))>::type::value }; 
};