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

11

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

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

这将导致以下错误信息:

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

我是在尝试不可能的事情吗,还是我的语法有误?


请查看我在此处发布的“Lambda2Delegate”解决方案:https://dev59.com/w2Ml5IYBdhLWcg3w96-L#26552573 - jenkas
3个回答

9
以下是我的解决方案,可以将lambda(以及任何函数对象 - 即任何可以调用 operator() 的东西)包装成委托。它有一些限制 - 具体来说,它不支持具有跟踪引用参数的委托(C++/CLI 中的 %,C# 中的 ref/out); 并且它对委托可以接受的参数数量有一个上限(因为 VC++2010 不支持可变参数模板) - 尽管代码可以轻松地调整以支持多达您需要的数量。
#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被扩展为普通类而不是refvalue类,因此其实际应用受到了一定的限制。在C++/CLI中,由于普通类不能包含托管类型(即没有对象句柄类型的成员、没有跟踪引用类型的成员和没有value class类型的成员),这意味着lambda也不能捕获任何这些类型的变量。我不知道有什么解决方法可以解决跟踪引用类型的问题。对于value class,您可以取一个非托管指针(如果需要,可以使用pin_ptr),并捕获它。

对于对象句柄,您可以将它们存储在gcroot<T>中,然后捕获它,但会有严重的性能影响-在我的测试中,通过gcroot<T>访问成员大约比使用普通对象句柄慢40倍。对于单个调用来说,绝对值并不多,但对于在循环中重复调用的东西-比如大多数LINQ算法-这将是致命的。但请注意,这仅适用于需要在lambda中捕获句柄的情况!如果您只是在内联中使用它来编写谓词或更新计数器,那么它将正常工作。


如果我对不同的东西进行捕获,但类型相同的lambda调用它似乎会失败并出现LNK2022。我猜生成的类型大小不同,而类型相同。是否有任何修复方法,或者我的理论完全错误? - Sarien
Sarien,你所说的“相同类型的lambda捕获不同内容”是什么意思?即使签名和捕获列表匹配,每个C++ lambda都有其自己独特的类型。 - Pavel Minaev

7

很抱歉,C++/CLI编译器没有更新以接受lambda语法。这相当具有讽刺意味,因为托管代码曾经拥有领先优势。


确实是这样。我想知道这是否与捕获子句语义有关,因为在CLR中不存在这种语义。据我所知,C# lambda表达式类似于[&, &this] - 就像lambda可以修改其父范围中的任何内容一样,因此他们可能只是规定C++ lambda必须具有这样的捕获子句。 - Igor Zevaka
CLR可以处理非托管指针(这就是&引用所转换的内容),因此它是可行的,只是不能被验证。我认为这只是他们不想花时间去实现的事情。我已经在Connect上记录了一个功能请求:https://connect.microsoft.com/VisualStudio/feedback/details/524356/add-managed-lambdas-to-c-cli - 如果您想要这个功能,请点赞。 - Pavel Minaev
1
哦,还有一件事。我确实有一些代码可供使用,可以将lambda(以及任何函数对象)包装成代理 - 这使您可以编写诸如:Func<int, int, int> ^ f1 = make_delegate([&](int x,int y){return x + y; });。但问题是,由于合成的lambda类型是未管理的,因此它不能包含托管类型 - 这意味着这样的lambda无法捕获托管对象引用( T ^)。当然,您可以使用gcroot <T>,但仍然不方便,并且我想知道性能影响。无论如何,如果您想要,我可以分享代码。 - Pavel Minaev
@absence:实现事件处理程序并不需要一个“一次性类”。一个简单的成员函数就可以完成任务。 - Hans Passant
@Pavel:是的,请。听起来很有趣。 - absence
显示剩余2条评论

-1

这个页面有一些关于C++的lambda表达式的例子:

http://msdn.microsoft.com/en-us/library/dd293608%28v=VS.100%29.aspx

微软VS2010 C++的改进看起来实际上是实现了C++0x lambda spec。因此,它们是纯非托管的,并且是lambda类型。

微软文档中没有任何暗示可以将C++ lambdas用作CLR lambdas的可能性。在这个阶段,我必须说您不能使用C++ lambdas作为托管委托的处理程序。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接