2010-11-13 13 views
7

Estoy tratando de crear una función que se pueda invocar con una lambda que tome 0, 1 o 2 argumentos. Como necesito el código para trabajar tanto en g ++ 4.5 como en vs2010 (que no admite plantillas variadic o conversiones lambda para punteros a funciones), la única idea que he encontrado es elegir qué implementación llamar en función de arity. Lo siguiente es mi conjetura no operativa sobre cómo debería verse esto. ¿Hay alguna manera de arreglar mi código o hay una mejor manera de hacerlo en general?C++ 0x: sobrecarga en 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

¿Puedes mostrar un ejemplo de código que llamaría a la función lambda? – Heatsink

Respuesta

16

una función lambda es un tipo de clase con un único operador llamada a la función. De este modo puede detectar la aridad de ese operador de llamada de función mediante la adopción de su dirección y el uso de resolución de sobrecarga para seleccionar qué función debe llamar:

#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;}); 
} 

Tenga cuidado sin embargo: esto no va a trabajar con los tipos de función, o tipos de clases que tener más de un operador de llamada de función, o operadores de llamada de función que no sean const.

+0

Muy bien, sin embargo, no necesita llamar realmente a la función (tal como está, restringe los tipos de parámetros) y le gustaría devolver el número de parámetros de 'do_stuff' (que puede llamarse mejor' arity'). – Motti

+1

Oh sí --- Acabo de hacer la llamada para demostrar que puedes. –

1

que cree que los siguientes funcionaría, pero no es así, la he publicado por dos razones.

  1. Para salvar a la gente el tiempo si tenían la misma idea
  2. Si alguien sabe por qué esto no funciona, no estoy 100% seguro de entender (aunque tengo mis sospechas)

Código sigue:

#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 sabe cómo envolver lambdas, pero * usted * tiene que proporcionar la firma correcta <...> directamente. Por ejemplo: dada "template void foo (vector );", no se puede pasar a foo una lista de inicializadores! El compilador sabe qué hacer con el vector :: vector (initializer_list ), pero tiene que saber T primero para llegar tan lejos. El compilador no puede comenzar desde initializer_list , mira un vector , y luego elige mágicamente T = X para que las cosas coincidan. Es al revés. –

-1

De esta manera funciona:

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 
} 

Funciona para todos los tipos exigibles (lambdas, funtores, etc.)

0

Tiempo de compilación medio de obtener la aridad de una función o un objeto función, incluyendo la de una 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 }; 
};