Workaround for not having lambdas that can capture managed variables
This is my solution for handling lambdas in C++/CLI, with a pretty straightforward syntax. I thought someone else might find it useful:
struct DefaultDelegate;
template<typename... Args>
value struct DelegateType;
template<typename Ret, typename... Args>
value struct DelegateType<DefaultDelegate, Ret, Args...>
{
delegate Ret MyDelegate(Args...);
typedef MyDelegate delegate_type;
};
template<typename Target, typename Ret, typename... Args>
value struct DelegateType<Target, Ret, Args...>
{
typedef Target delegate_type;
};
template<typename Lambda>
ref class LambdaWrapper
{
public:
LambdaWrapper(Lambda &&lambda) : func(new Lambda(std::forward<Lambda>(lambda))) {}
!LambdaWrapper() { delete func; }
~LambdaWrapper() { delete func; }
template<typename Ret, typename... Args>
Ret CallLambda(Args... args) { return (*func)(args...); }
private:
Lambda *func;
};
template<typename Target, typename Lambda, typename Ret, typename... Args>
auto _toDelegate(Lambda &&lambda, Ret(Lambda::*func)(Args...))
{
LambdaWrapper<Lambda> ^lw = gcnew LambdaWrapper<Lambda>(std::forward<Lambda>(lambda));
return gcnew typename DelegateType<Target, Ret, Args...>::delegate_type(lw, &LambdaWrapper<Lambda>::CallLambda<Ret, Args...>);
}
template<typename Target, typename Lambda, typename Ret, typename... Args>
auto _toDelegate(Lambda &&lambda, Ret(Lambda::*func)(Args...) const)
{
LambdaWrapper<Lambda> ^lw = gcnew LambdaWrapper<Lambda>(std::forward<Lambda>(lambda));
return gcnew typename DelegateType<Target, Ret, Args...>::delegate_type(lw, &LambdaWrapper<Lambda>::CallLambda<Ret, Args...>);
}
template<typename Target, typename Lambda>
auto toDelegate(Lambda &&lambda)
{
return _toDelegate<Target>(std::forward<Lambda>(lambda), &Lambda::operator());
}
Usage:
int k = 2;
//If you need a generic delegate
Delegate ^d = toDelegate<DefaultDelegate>([k](int i, int j) ->int {return k * (i + j); });
//If you need a delegate of a specific type
MyDelegate ^d = toDelegate<MyDelegate>([k](int i, int j) ->int {return k * (i + j); });
I wrote Lamda2Delegate struct for this purpose. Actually it converts c++11 lambda to any .net delegate.
The example of usage:
Thread^ TestLambaWrapper()
{
gcroot<String ^> str = "Testext";
int i = 12345;
Thread^ newThread = gcnew Thread(
Lambda2Delegate<ParameterizedThreadStart>() = [&, str](Object ^ str2)
{
Sleep(2000);
Console::WriteLine("Thread output = {0} {1} {2}", str, i, str2);
}
);
newThread->Start("Nahnah");
return newThread;
}
For your case:
gcroot<A^> a = gcnew A();
Func<A^> ^ aFunc = Lambda2Delegate<>() = [a](){ return (A^)a; };
auto a2 = aFunc();
To capture managed classes you need to wrap them with gcroot, and capture explicitly by value.
And the Lambda2Delegate.h itself
#pragma once
#ifdef _MANAGED
struct AutoDetectDelegateType {};
template<typename TDelegate, typename TLambda, typename TRet, typename ...TParams>
ref class LambdaHolder;
template<typename TDelegate, typename TLambda, typename TRet, typename ...TParams>
ref class LambdaHolder
{
public:
inline LambdaHolder(const TLambda % func) { m_func = new TLambda(func); }
!LambdaHolder() { delete m_func; }
~LambdaHolder() { !LambdaHolder(); }
public:
TRet Callback(TParams... params) { return (*m_func)(params...); }
operator TDelegate ^ () { return gcnew TDelegate(this, &LambdaHolder::Callback); }
private:
TLambda * m_func;
};
template<typename TLambda, typename TRet, typename ...TParams>
ref class LambdaHolder<AutoDetectDelegateType, TLambda, TRet, TParams...>
{
public:
inline LambdaHolder(const TLambda % func) { m_func = new TLambda(func); }
!LambdaHolder() { delete m_func; }
~LambdaHolder() { !LambdaHolder(); }
public:
TRet Callback(TParams... params) { return (*m_func)(params...); }
template<typename TDelegate>
operator TDelegate ^ () { return gcnew TDelegate(this, &LambdaHolder::Callback); }
private:
TLambda * m_func;
};
template <typename TDelegate, typename TLambda>
struct get_labmda_holder : public get_labmda_holder < TDelegate, decltype(&TLambda::operator()) > {};
template <typename TDelegate, typename TLambda, typename TRet, typename... TParams>
struct get_labmda_holder < TDelegate, TRet(__clrcall TLambda::*)(TParams...) const >
{
typedef LambdaHolder<TDelegate, TLambda, TRet, TParams...> TLambdaHolder;
};
template <typename TDelegate, typename TLambda, typename TRet, typename... TParams>
struct get_labmda_holder < TDelegate, TRet(__clrcall TLambda::*)(TParams...) >
{
typedef LambdaHolder<TDelegate, TLambda, TRet, TParams...> TLambdaHolder;
};
template <typename TDelegate, typename TLambda, typename TRet, typename... TParams>
struct get_labmda_holder < TDelegate, TRet(__thiscall TLambda::*)(TParams...) const >
{
typedef LambdaHolder<TDelegate, TLambda, TRet, TParams...> TLambdaHolder;
};
template <typename TDelegate, typename TLambda, typename TRet, typename... TParams>
struct get_labmda_holder < TDelegate, TRet(__thiscall TLambda::*)(TParams...)>
{
typedef LambdaHolder<TDelegate, TLambda, TRet, TParams...> TLambdaHolder;
};
template<typename TDelegate = AutoDetectDelegateType>
struct Lambda2Delegate
{
template<typename TLambda>
typename get_labmda_holder<TDelegate, TLambda>::TLambdaHolder ^ operator = (const TLambda % func)
{
return gcnew get_labmda_holder<TDelegate, TLambda>::TLambdaHolder(func);
}
};
#endif
UPDATE: It is not possible to declare c++ lambda function inside managed member function, but there is workaround - use static member function:
ref class S
{
public:
int F(System::String ^ str)
{
return F(this, str);
}
private:
//static function declaring c++ lambda
static int F(S ^ pThis, System::String ^ str)
{
gcroot<System::String ^> localStr = "local string";
System::Func<System::String ^, int> ^ func = Lambda2Delegate<>() = [=](System::String ^ str)
{
System::Console::WriteLine(str);
System::Console::WriteLine(localStr);
return str->Length;
};
return func(str);
}
};
If you look at a decompilation of a C# lambda, you'll see that the C# compiler does the same thing as your option #2. It's annoying to create a bunch of single-use classes, but that's what I'd recommend.
With a C# lambda, when it creates the nested class instance, it uses that everywhere instead of the local variable. Keep that in mind as you write the method that uses the nested class.