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.
Compruebe mi solución "Lambda2Delegate" publicado aquí http://stackoverflow.com/a/26552573/2604941 –