views:

541

answers:

3

Is it possible to use the new lambda expressions in Visual C++ 2010 as CLR event handlers? I've tried the following code:

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

It results in the following error message:

error C3364: 'System::EventHandler' : invalid argument for delegate constructor; delegate target needs to be a pointer to a member function

Am I attempting the impossible, or is simply my syntax wrong?

A: 

This page has a few examples of lambdas for C++:

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

Microsoft VS2010 C++ improvements look like they actually implement C++0x lambda spec. As such they are purely unmanaged and are of type lambda.

There is nothing in the Microsoft documentation that hints at possibility of using C++ lambdas as CLR lambdas. At this stage I have to say that you cannot use C++ lambdas as handlers for managed delegates.

Igor Zevaka
+1  A: 

No can do, the C++/CLI compiler didn't get updated to accept the lambda syntax. Fairly ironic btw given the head-start that managed code had.

Hans Passant
Igor Zevaka
That's too bad, it looks a bit messy with a throw-away class inside the function that creates the handler, just to contain a member function. :/
absence
Pavel Minaev
Oh, one more thing. I do actually have some code lying around that lets you wrap lambdas (and any function objects, really) into delegates - it lets you write things such as: `Func<int, int, int>^ f1 = make_delegate([ });`. The problem with it is that, since synthesized lambda type is unmanaged, it cannot contain managed types - which means that such lambda cannot capture managed object references (`T^`). You can use `gcroot<T>` instead, of course, but it's still inconvenient, and I do wonder about perf implications. Anyway, I can share the code if you want.
Pavel Minaev
@absence: it doesn't take a "throw-away class" to implement an event handler. A simple member function gets the job done.
Hans Passant
@Hans: this requires a class in the first place (who says that the code registering an event has to be in a class?), and then it also requires a `ref class`. Furthermore, a lambda also captures the surrounding environment, and you have to do it manually for a member function.
Pavel Minaev
@Pavel: Yes please, it sounds interesting.
absence
Added as a separate answer.
Pavel Minaev
+2  A: 

The following is my solution that allows one to wrap lambdas (as well as any function objects - i.e. anything on which operator() can be called) into delegates. It has some limits - specifically, it doesn't support delegates with tracking reference parameters (% in C++/CLI, ref/out in C#); and it has an upper limit on the number of parameters the delegate can take (because VC++2010 doesn't suppport vararg templates) - though the code can be trivially adjusted to support up to as many as you want.

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

Sample usage:

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

While this technically does what you want, the practical applications are somewhat limited due to the fact that C++0x lambdas are expanded into plain classes, not ref or value ones. Since plain classes cannot contain managed types in C++/CLI (i.e. no members of object handle type, no members of tracking reference type, and no members of value class type), this means that lambdas cannot capture any variables of those types, either. There is no workaround I'm aware of for tracking references. For value class, you can take an unmanaged pointer to it (pin_ptr if needed), and capture that.

For object handles, you can store them in gcroot<T>, and capture that - but there are severe performance implications - in my tests, accessing a member via gcroot<T> is about 40x times slower than doing it using a plain object handle. It's actually not much in absolute measure for a single call, but for something that is called repeatedly in a loop - say, most LINQ algorithms - it would be a killer. But note that this only applies when you need to capture a handle in the lambda! If you just use it to write a predicate inline, or to update a counter, it'll work just fine.

Pavel Minaev
Thanks! I'll keep the performance implications in mind.
absence