Workaround for not having lambdas that can capture managed variables

前端 未结 3 348
伪装坚强ぢ
伪装坚强ぢ 2020-12-31 10:29

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

相关标签:
3条回答
  • 2020-12-31 11:10

    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.

    0 讨论(0)
  • 2020-12-31 11:11

    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); });
    
    0 讨论(0)
  • 2020-12-31 11:25

    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);
            }
        };
    
    0 讨论(0)
提交回复
热议问题