Лямбда-выражения в виде делегатов / обработчиков событий CLR (.NET) в Visual C ++ 2010 - PullRequest
11 голосов
/ 06 мая 2010

Можно ли использовать новые лямбда-выражения в Visual C ++ 2010 в качестве обработчиков событий CLR? Я пробовал следующий код:

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

Это приводит к следующему сообщению об ошибке:

ошибка C3364: 'System :: EventHandler': неверный аргумент для конструктора делегата; цель делегата должна быть указателем на функцию-член

Я пытаюсь сделать невозможное или просто мой синтаксис неверен?

Ответы [ 3 ]

9 голосов
/ 06 мая 2010

Следующее - мое решение, которое позволяет заключать лямбды (а также любые объекты функций - то есть все, что может вызываться 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 расширены в простые классы, а не ref или value. Так как простые классы не могут содержать управляемые типы в C ++ / CLI (то есть, нет элементов типа дескриптора объекта, нет элементов типа ссылки отслеживания и элементов типа value class), это означает, что лямбда-выражения не могут захватывать любые переменные этих типов, либо , Я не знаю обходного пути для отслеживания ссылок. Для value class вы можете взять неуправляемый указатель на него (pin_ptr при необходимости) и захватить его.

Для дескрипторов объектов вы можете сохранить их в gcroot<T> и зафиксировать это - но это имеет серьезные последствия для производительности - в моих тестах доступ к элементу через gcroot<T> примерно в 40 раз медленнее, чем при использовании простого объекта справиться. На самом деле это не слишком много в абсолютном выражении для одного вызова, но для чего-то, что вызывается в цикле несколько раз - скажем, для большинства алгоритмов LINQ - это будет убийственно. Но учтите, что это применимо только тогда, когда вам нужно захватить ручку в лямбде! Если вы просто используете его для записи встроенного предиката или для обновления счетчика, он будет работать просто отлично.

7 голосов
/ 06 мая 2010

Нет, ничего не поделаешь, компилятор C ++ / CLI не обновился, чтобы принять лямбда-синтаксис. Совершенно иронично, кстати, учитывая преимущество, которое имел управляемый код.

0 голосов
/ 06 мая 2010

На этой странице есть несколько примеров лямбд для C ++:

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

Улучшения Microsoft VS2010 C ++ выглядят так, как будто они действительно реализуют C ++ 0x лямбда-спецификация . Как таковые они являются чисто неуправляемыми и относятся к типу lambda.

В документации Microsoft нет ничего, что указывало бы на возможность использования лямбд C ++ в качестве лямбд CLR. На этом этапе я должен сказать, что вы не можете использовать лямбды C ++ в качестве обработчиков для управляемых делегатов.

...