Workaround for not having lambdas that can capture

2019-02-09 14:38发布

问题:

In C++/CLI, you cannot create managed lambdas (like you can in C#), and thus you can't capture managed variables. You can create regular methods (rather than lambdas), but you are still left without being able to capture managed variables.

Is there a standard workaround to employ in C++/CLI code? In other words I'm looking for a standard pattern I could use in C++/CLI to do the following from C#:

class A { }

class B
{
    void Foo()
    {
        A a = new A();
        Func<A> aFunc = () => a; // Captures a
    }
}

I could

  • Create a member variable for each variable I want to capture, and then in the delegate use that member variable. This wouldn't work in the general case as you might have two invocations of the method that want to work on different captured a's, but it would work for the common case.
  • Create a nested class that does the capturing in its ctor, and then use a method of this nested class as the delegate. This should work in the general case, but it means I need a nested class every time I want to capture different variables.

Question: Is there a better option than the ones above, or which option above would be your go-to approach?

Related Questions:

  • Lambda expressions as CLR (.NET) delegates / event handlers in Visual C++ 2010
  • Lambdas in C++/CLI

回答1:

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.



回答2:

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


回答3:

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


标签: c++-cli