Quanto segue è la mia soluzione che consente di avvolgere lambda (così come qualsiasi oggetto funzione - cioè qualsiasi cosa su cui è possibile chiamare operator()
) in delegati. Ha alcuni limiti - in particolare, non supporta i delegati con i parametri di riferimento di tracciamento (%
in C++/CLI, ref
/out
in C#); e ha un limite superiore al numero di parametri che il delegato può prendere (perché VC++ 2010 non supporta i modelli vararg) - sebbene il codice possa essere banalmente aggiustato per supportare fino a quanti ne vuoi.
#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);
}
utilizzo Esempio:
Func<int, String^, int>^ f2 = make_delegate([&](int x, String^ y) -> int {
Console::WriteLine("Func {0} {1}", x, y);
return 2;
});
Mentre questo tecnicamente ciò che si desidera, le applicazioni pratiche sono piuttosto limitate a causa del fatto che il C++ 0x lambda sono espanse in classi normali, non ref
o value
quelli. Poiché le classi normali non possono contenere tipi gestiti in C++/CLI (cioè nessun membro del tipo di handle dell'oggetto, nessun membro del tipo di riferimento di tracciamento e nessun membro del tipo value class
), ciò significa che lambda non può acquisire alcuna variabile di questi tipi. Non sono disponibili soluzioni per i riferimenti di tracciamento. Per value class
, è possibile utilizzare un puntatore non gestito (pin_ptr
se necessario) e acquisirlo.
Per le maniglie degli oggetti, è possibile memorizzarli in gcroot<T>
e acquisirli, ma ci sono gravi implicazioni sulle prestazioni: nei miei test, l'accesso a un membro tramite gcroot<T>
è circa 40 volte più lento rispetto all'utilizzo di un handle di oggetto normale. In realtà non è molto in assoluto per una singola chiamata, ma per qualcosa che viene chiamato ripetutamente in un ciclo - per esempio, la maggior parte degli algoritmi LINQ - sarebbe un killer. Nota che questo si applica solo quando devi catturare una maniglia nella lambda! Se lo usi solo per scrivere un predicato in linea o per aggiornare un contatore, funzionerà perfettamente.
Controlla la mia soluzione "Lambda2Delegate" pubblicato qui http://stackoverflow.com/a/26552573/2604941 –