Лямбда-выражения как делегаты CLR (.NET) / обработчики событий в Visual C++ 2010
можно ли использовать новые лямбда-выражения в Visual C++ 2010 в качестве обработчиков событий среды CLR? Я пробовал следующий код:
SomeEvent += gcnew EventHandler(
[] (Object^ sender, EventArgs^ e) {
// code here
}
);
это приводит к следующему сообщению об ошибке:
ошибка C3364: 'System:: EventHandler': недопустимый аргумент для конструктора делегата; цель делегата должна быть указателем на функцию-член
Я пытаюсь невозможное, или просто мой синтаксис неправильный?
3 ответов
нет, компилятор C++/CLI не обновился, чтобы принять синтаксис лямбда-кода. Довольно иронично, кстати, учитывая начало, которое имел управляемый код.
следующее мое решение, которое позволяет обернуть лямбды (а также любые объекты функций - т. е. все, на чем 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), это означает, что лямбда также не удается захватить какие-либо переменные этих типов. Нет никакого обходного пути, о котором я знаю для отслеживания ссылок. Для value class
, вы можете взять неуправляемый указатель на него (pin_ptr
при необходимости), и захватить это.
для дескрипторов объектов вы можете хранить их в gcroot<T>
, и захватить что - но есть серьезные последствия для производительности - в моих тестах, доступ к члену через gcroot<T>
примерно в 40 раз медленнее, чем при использовании простого дескриптора объекта. Это на самом деле не так много в абсолютных мера для одного вызова, но для чего - то, что вызывается неоднократно в цикле - скажем, большинство алгоритмов LINQ-это будет убийца. Но обратите внимание, что это применимо только тогда, когда вам нужно захватить дескриптор в лямбде! Если вы просто используете его для записи предиката inline или для обновления счетчика, он будет работать нормально.
на этой странице есть несколько примеров lambdas для C++:
http://msdn.microsoft.com/en-us/library/dd293608%28v=VS.100%29.aspx
улучшения Microsoft VS2010 c++ выглядят так, как будто они фактически реализуют C++0x лямбда-спецификация. Как таковые они являются чисто неуправляемыми и имеют тип lambda
.
в документации Microsoft нет ничего, что намекает на возможность использования C++ lambdas в качестве CLR lambdas. На этом этапе я должен сказать что нельзя использовать лямбды C++ в качестве обработчиков для управляемых делегатов.