Dot-Net

Visual C++ 2010 中作為 CLR (.NET) 委託/事件處理程序的 Lambda 表達式

  • May 6, 2010

是否可以將 Visual C++ 2010 中的新 lambda 表達式用作 CLR 事件處理程序?我試過以下程式碼:

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

它會導致以下錯誤消息:

錯誤 C3364:“System::EventHandler”:委託建構子的參數無效;委託目標需要是指向成員函式的指針

我是在嘗試不可能的事情,還是只是我的語法錯誤?

不行,C++/CLI 編譯器沒有更新以接受 lambda 語法。順便說一句,考慮到託管程式碼的領先優勢,這頗具諷刺意味。

以下是我的解決方案,它允許將 lambdas(以及任何函式對象 - 即任何operator()可以呼叫的對象)包裝到委託中。它有一些限制 - 具體來說,它不支持帶有跟踪引用參數的委託(%在 C++/CLI 中,ref/out在 C# 中);並且它對委託可以採用的參數數量有上限(因為 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 lambda 被擴展為普通類,而不是ref或類,因此實際應用受到了一定的限制value。由於普通類不能包含 C++/CLI 中的託管類型(即沒有對象句柄類型的成員、沒有跟踪引用類型的成員和沒有value class類型的成員),這意味著 lambda 也不能擷取這些類型的任何變數。我知道沒有用於跟踪參考的解決方法。對於value class,您可以獲取一個指向它的非託管指針(pin_ptr如果需要),然後擷取它。

對於對象句柄,您可以將它們儲存在 中gcroot<T>,並擷取它們——但會影響性能——在我的測試中,通過訪問成員gcroot<T>的速度比使用普通對象句柄慢大約 40 倍。實際上,對於單個呼叫來說,絕對量並不多,但對於在循環中重複呼叫的東西——比如說,大多數 LINQ 算法——這將是一個殺手。但請注意,這只適用於需要在 lambda 中擷取句柄的情況!如果你只是用它來寫一個謂詞內聯,或者更新一個計數器,它會工作得很好。

引用自:https://stackoverflow.com/questions/2777445