问题 Lambda表达式作为Visual C ++ 2010中的CLR(.NET)委托/事件处理程序


是否可以在Visual C ++ 2010中将新的lambda表达式用作CLR事件处理程序?我试过以下代码:

SomeEvent += gcnew EventHandler(
    [] (Object^ sender, EventArgs^ e) {
        // code here
    }
);

它会导致以下错误消息:

错误C3364:'System :: EventHandler':委托构造函数的参数无效;委托目标需要是指向成员函数的指针

我尝试不可能,或者只是我的语法错了?


6613
now


起源

检查我的解决方案“Lambda2Delegate”在这里发布 stackoverflow.com/a/26552573/2604941 - Evgeny Mamontov


答案:


没有办法,C ++ / CLI编译器没有得到更新以接受lambda语法。鉴于托管代码具有先机性,因此具有讽刺意味。


7
2018-05-06 00:01



真的是。我想知道这是否与捕获子句语义有关,这在CLR中是不存在的。据我所知,C#lambdas就是这样的 [&, &this]  - 因为在lambda中可以修改其父级范围内的任何内容,因此他们可能只是强制要求C ++ lambdas必须具有这样的capture子句。 - Igor Zevaka
这太糟糕了,在创建处理程序的函数内部抛出一个类看起来有点乱,只是为了包含一个成员函数。 :/ - absence
CLR可以处理非托管指针(这是&-ferences会被转换为什么),所以它可以完成 - 它只是不可验证。我认为这不是他们想要花时间实施的东西。我为此记录了一个Connect功能请求: connect.microsoft.com/VisualStudio/feedback/details/524356/...  - 如果你想要这个,请upvote。 - Pavel Minaev
哦,还有一件事。我确实有一些代码可以让你将lambdas(以及任何函数对象,真的)包装到委托中 - 它允许你编写如下内容: Func<int, int, int>^ f1 = make_delegate([&](int x, int y) { return x + y; });。它的问题在于,由于合成的lambda类型是非托管的,它不能包含托管类型 - 这意味着这样的lambda无法捕获托管对象引用(T^)。您可以使用 gcroot<T> 相反,当然,但它仍然不方便,我确实想知道性能影响。无论如何,如果你愿意,我可以分享代码。 - Pavel Minaev
@absence:它不需要“丢弃类”来实现事件处理程序。一个简单的成员函数可以完成工作。 - Hans Passant


以下是我的解决方案,允许一个人包装lambda(以及任何函数对象 - 即任何东西 operator() 可以被称为代表。它有一些限制 - 具体来说,它不支持具有跟踪参考参数的代表(% 在C ++ / CLI中, ref/out 在C#);并且它对委托可以采用的参数数量有一个上限(因为VC ++ 2010不支持vararg模板) - 尽管可以通过简单的方式调整代码以支持尽可能多的代码。

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

样品用法:

Func<int, String^, int>^ f2 = make_delegate([&](int x, String^ y) -> int {
    Console::WriteLine("Func {0} {1}", x, y);
    return 2;
});

虽然这在技术上做到了你想要的,但实际的应用程序有点受限,因为C ++ 0x lambdas被扩展为普通类,而不是 ref 要么 value 那些。由于普通类不能在C ++ / CLI中包含托管类型(即没有对象句柄类型的成员,没有跟踪引用类型的成员,也没有成员 value class type),这意味着lambdas也无法捕获这些类型的任何变量。我没有找到跟踪引用的解决方法。对于 value class,你可以拿一个非托管指针(pin_ptr 如果需要),并抓住它。

对于对象句柄,您可以将它们存储在 gcroot<T>并且捕捉到了 - 但是在我的测试中,存在严重的性能影响,通过访问成员 gcr