最近看到某位網友的問題:「template 的函數的指標要怎麼傳給 template的函數?」由於沒有額外的說明,所以我不太確定他遇到的困難點在哪裡。

先來看看熱身題,假如存在下面程式片斷,要如何傳遞 Output1() 給 Call() 呢?:

template <class T>
void Output1(const T& obj)
{
   cout << obj << endl;
}

template <class Fun, class T>
void Call(Fun f, const T& obj)
{
   f(obj);
}

我想對熟悉 C++ 的人來說,答案應該非常簡單。只要像這樣即可:

Call(&Output1<int>, 12345);
Call(&Output1<const char*>, "goodbye world.");

這裡唯一比較重要的觀念是 template 並非真正的程式碼,必須要實體化之後編譯器才會產生真實的 function 或 class。更具體的說,Output1<int> 和 Output1<const char*> 都是真實存在的函數,但 Output1 並不是,它只是個模板。所以像下面這種寫法是錯誤的

Call(&Output1, 12345);

因為在還沒實體化之前函數並不真正存在,當然也沒有位址,編譯器根本不知道該傳遞甚麼東西給 Call。這也告訴我們,儘管 Output1 可以接受任何型別,但一旦我們想得到函數指標,Output1 就必須綁定到特定的型別上,所以不再是泛型。

但有的時候我們想傳遞的並不是某個特定的函數指標,而是這個泛用的功能,我猜網友原先的問題在這裡。

對純函數而言,這個問題大概無解,因為我們沒有其它的方式可以指涉一個 function template。但如果放寬標準到所有「像函數的東西」,那麼還是大有可為。最直接的觀察,原問題卡在傳遞函數指標之前必須要具現化,而造成型別綁定。如果能夠將這個綁定推遲到呼叫的時候,那麼問題就解決了。

struct Output2
{
   template <class T>
   void operator()(const T& obj)
   {
      cout << obj << endl;
   }
};

template <class Fun, class T>
void Call(Fun f, const T& obj)
{
   f("header --- ");
   f(obj);
}

.....

Call(Output2(), 12345);

這個方法還可以得到另一個額外的好處,如果函數本身可以被inline的話,那麼最佳化之後就不會有函數呼叫的overhead。反之透過函數指標則無論如何都避不了間接呼叫的開銷,即使函數加了 inline 修飾也沒用。

對於單純「像函數的東西」這大概是最簡單的方案了,更複雜的用途可能必須依靠 template template parameter 或者 rebinding 來解決。篇幅有限,有機會再介紹。

arrow
arrow
    全站熱搜

    novus 發表在 痞客邦 留言(1) 人氣()