2010-05-05 16 views
11

¿Es posible utilizar las nuevas expresiones lambda en Visual C++ 2010 como controladores de eventos CLR? He intentado el siguiente código:Expresiones Lambda como delegados/controladores de eventos CLR (.NET) en Visual C++ 2010

SomeEvent += gcnew EventHandler(
    [] (Object^ sender, EventArgs^ e) { 
     // code here 
    } 
); 

El resultado es el siguiente mensaje de error:

C3364 de error: ':: Sistema manejador de sucesos': argumento no válido para el constructor delegado; delegar el destino debe ser un puntero a una función miembro

¿Estoy intentando lo imposible, o simplemente es incorrecta mi sintaxis?

+0

Compruebe mi solución "Lambda2Delegate" publicado aquí http://stackoverflow.com/a/26552573/2604941 –

Respuesta

7

No se puede hacer, el compilador C++/CLI no se actualizó para aceptar la sintaxis lambda. Bastante irónico, por cierto, teniendo en cuenta la ventaja que tuvo el código administrado.

+0

Es de hecho. Me pregunto si esto tiene algo que ver con la semántica de la cláusula de captura, que no existe en CLR. Por lo que yo entiendo C# lambdas son algo así como '[&, y esto]' - como en lambda puede modificar lo que está en el alcance de su padre, entonces podrían haber ordenado que C++ lambdas debe tener tal cláusula de captura. –

+0

Eso está muy mal, se ve un poco desordenado con una clase descartable dentro de la función que crea el controlador, solo para contener una función de miembro. :/ – absence

+0

CLR puede tratar con punteros no administrados (que es a lo que se traduce & -references), por lo que podría hacerse, simplemente no sería verificable. Creo que simplemente no es algo que querían tomarse un tiempo para implementar. Tengo una solicitud de función de conexión registrada para esto: https://connect.microsoft.com/VisualStudio/feedback/details/524356/add-managed-lambdas-to-c-cli - upvote si quieres esto. –

-1

Esta página tiene algunos ejemplos de lambdas para C++:

http://msdn.microsoft.com/en-us/library/dd293608%28v=VS.100%29.aspx

mejoras Microsoft VS2010 C++ parecen que implementan realmente C++0x lambda spec. Como tales, son puramente no administrados y son del tipo lambda.

No hay nada en la documentación de Microsoft que sugiera la posibilidad de utilizar C++ lambdas como CLR lambdas. En esta etapa, debo decir que no puede usar C++ lambdas como controladores para delegados gestionados.

9

La siguiente es mi solución que permite envolver lambdas (así como cualquier objeto de función, es decir, cualquier elemento en el que se llame operator()) en delegados. Tiene algunos límites: específicamente, no admite delegados con parámetros de referencia de seguimiento (% en C++/CLI, ref/out en C#); y tiene un límite superior en la cantidad de parámetros que el delegado puede tomar (porque VC++ 2010 no soporta plantillas vararg), aunque el código puede ajustarse de manera trivial para admitir tantos como desee.

#pragma once 

#include <new> 
#include <type_traits> 

namespace detail 
{ 
    struct return_type_helper 
    { 
    private: 

     template<class D> 
     struct dependent_false { enum { value = false }; }; 

     template <class D> 
     struct illegal_delegate_type 
     { 
      static_assert(dependent_false<D>::value, "Delegates with more than 2 parameters, or with parameters of tracking reference types (T%), are not supported."); 
     }; 

     struct anything 
     { 
      template<class T> 
      operator T() const; 
     }; 

    public: 

     template<class D> 
     static decltype(static_cast<D^>(nullptr)()) dummy(int(*)[1]); 

     template<class D> 
     static decltype(static_cast<D^>(nullptr)(anything())) dummy(int(*)[2]); 

     template<class D> 
     static decltype(static_cast<D^>(nullptr)(anything(), anything())) dummy(int(*)[3]); 

     template <class D> 
     static illegal_delegate_type<D> dummy(...); 
    }; 


    template<class Func, class Aligner = char, bool Match = (std::tr1::alignment_of<Func>::value == std::tr1::alignment_of<Aligner>::value)> 
    struct aligner 
    { 
     static_assert(Match, "Function object has unsupported alignment"); 
    }; 

    template<class Func, class Aligner> 
    struct aligner<Func, Aligner, true> 
    { 
     typedef Aligner type; 
    }; 

    template<class Func> 
    struct aligner<Func, char, false> : aligner<Func, short> 
    { 
    }; 

    template<class Func> 
    struct aligner<Func, short, false> : aligner<Func, int> 
    { 
    }; 

    template<class Func> 
    struct aligner<Func, int, false> : aligner<Func, long> 
    { 
    }; 

    template<class Func> 
    struct aligner<Func, long, false> : aligner<Func, long long> 
    { 
    }; 

    template<class Func> 
    struct aligner<Func, long long, false> : aligner<Func, double> 
    { 
    }; 

    template<class Func> 
    struct aligner<Func, double, false> : aligner<Func, void*> 
    { 
    }; 


    template<class F> 
    ref class lambda_wrapper 
    { 
    public: 

     lambda_wrapper(const F& f) 
     { 
      pin_ptr<F> pf = (interior_ptr<F>)&f_storage; 
      new(pf) F(f); 
     } 

     ~lambda_wrapper() 
     { 
      pin_ptr<F> pf = (interior_ptr<F>)&f_storage; 
      pf->~F(); 
     } 

     template <class D> 
     operator D^() 
     { 
      D^ d = nullptr; 
      return gcnew D(this, &lambda_wrapper<F>::invoke<decltype(return_type_helper::dummy<D>(0))>); 
     } 

    private: 

     template<class T> 
     [System::Runtime::InteropServices::StructLayout(System::Runtime::InteropServices::LayoutKind::Sequential, Size = sizeof(T))] 
     value struct embedded_storage 
     { 
     private: 
      typename aligner<T>::type dummy; 
     }; 


     embedded_storage<F> f_storage; 

     template<class R> 
     R invoke() 
     { 
      pin_ptr<F> pf = (interior_ptr<F>)&f_storage; 
      return (*pf)(); 
     } 

     template<class R, class A1> 
     R invoke(A1 a1) 
     { 
      pin_ptr<F> pf = (interior_ptr<F>)&f_storage; 
      return (*pf)(a1); 
     } 

     template<class R, class A1, class A2> 
     R invoke(A1 a1, A2 a2) 
     { 
      pin_ptr<F> pf = (interior_ptr<F>)&f_storage; 
      return (*pf)(a1, a2); 
     } 
    }; 
} 

template<class F> 
detail::lambda_wrapper<F>^ make_delegate(F f) 
{ 
    return gcnew detail::lambda_wrapper<F>(f); 
} 

Ejemplo de uso:

Func<int, String^, int>^ f2 = make_delegate([&](int x, String^ y) -> int { 
    Console::WriteLine("Func {0} {1}", x, y); 
    return 2; 
}); 

Si bien esta técnica hace lo que quiere, las aplicaciones prácticas son algo limitadas debido al hecho de que C++ 0x lambdas se expanden en las clases de fricción, no ref o value unos. Como las clases simples no pueden contener tipos administrados en C++/CLI (es decir, no hay miembros del tipo de identificador de objeto, no hay miembros del tipo de referencia de seguimiento ni miembros del tipo value class), esto significa que lambdas tampoco puede capturar ninguna variable de esos tipos. No hay ninguna solución que conozca para rastrear referencias. Para value class, puede tomar un puntero no administrado (pin_ptr si es necesario) y capturarlo.

Para identificadores de objetos, puede almacenarlos en gcroot<T>, y capturar eso, pero hay implicaciones de rendimiento severas, en mis pruebas, acceder a un miembro a través de gcroot<T> es aproximadamente 40 veces más lento que hacerlo con un manejador de objeto simple. En realidad, no se trata en gran medida de una sola llamada, sino de algo que se llama repetidamente en un ciclo, por ejemplo, la mayoría de los algoritmos LINQ, sería un asesino. ¡Pero tenga en cuenta que esto solo se aplica cuando necesita capturar un identificador en el lambda! Si solo lo usa para escribir un predicado en línea, o para actualizar un contador, funcionará bien.

+0

Gracias! Tendré en cuenta las implicaciones de rendimiento. – absence

+0

Esto parece fallar con LNK2022 si lo llamo lambdas con el mismo tipo pero que captura cosas diferentes. Supongo que el tamaño del tipo generado es diferente, mientras que el tipo es el mismo. ¿Hay alguna solución para esto o simplemente estoy de acuerdo con mi teoría de todos modos? – Sarien

+0

Sarien, ¿qué quieres decir con "lambdas del mismo tipo capturando cosas diferentes"? Cada lambda C++ tiene su propio tipo único, incluso si las firmas coinciden, e incluso si las listas de captura coinciden. –