2016年3月9日 星期三

認識SFINAE(補充)

作者:Bartlomiej Filipek
原文出處:http://www.bfilipek.com/2016/02/sfinae-followup.html


SFINAE Followup



我上一篇發的SFINAE的反應似乎不錯!我收到非常寶貴的評論跟建議。於是動筆寫這篇來做為補充。

運用現代C++表達
在其中意見回覆中STL (Stephan T. Lavavej) 說我在文章中的解決方案是舊式的C++風格。那最新現代的風格會有什麼效果呢?

decltype

decltype 推斷出運算式(expression)的返回型別的強大工具我已經使用過它了:
template <typename C> 
static YesType& test( decltype(&C::ToString) ) ;

decltype會返回C::ToString成員方法的型別(如果類別內存在這方法的話)。

declval

declval 的效用是讓你真的建構一個物件就能呼叫T裡面的方法。在之前的例子中我們能用它來推斷方法的返回型別:
decltype(declval<T>().toString())

constexpr

constexpr 會建議編譯器在編譯期就算出運算式的結果(如果可能的話)沒有它的話可能得在執行期檢驗我們的成員方法了。所以新的風格建議對大多數的methods都增加constexpr


void_t

言講影片:


從29分開始,尤其39分段落

這是令人驚奇的超編程(meta-programming)典範!我不想去講解它,您只需看影片就能理解void_t的概念思維! :)

detection 慣用法

Walter E. Brown計劃一個具完全通用的class,能用來檢測介面跟其它class屬性。當然,大部份是以void_t技術為基礎來實現


檢驗返回型別
上一次我丟出未解問題:如何約束ToString()方法的返回型別。我之前的原始碼能檢驗方法的名稱,但無法檢驗它的返回型別
Björn Fahller 給了我以下的回答: (在文章下面的評論)
template <typename T>
class has_string{
  template <typename U>
  static constexpr std::false_type test(...) { return {};}
  template <typename U>
  static constexpr auto test(U* u) ->
    typename std::is_same<std::string, decltype(u->to_string())>::type { return {}; }
public:
  static constexpr bool value = test<T>(nullptr);
};

class with_string {
public:
  std::string to_string();
};

class wrong_string{
public:
  const char* to_string();
};

int main() {
  std::cout
    << has_string<int>::value
    << has_string<with_string>::value
    << has_string<wrong_string>::value << '\n';
}

此程式將輸出:

010

在test方法中檢驗to_string()返回型別是否為我們渴望的std::string()。這class包含兩個層級的測試 : 先使用SFINAE -檢驗class有無to_string方法(若無,我們則退回採用test(...))。在這之後,檢驗返回型別是不是我們想要的。最終當我們傳入錯誤的class或class裡的to_string()返回錯誤的型別時,都會從has_string<T>::value中獲得false。這範例太棒了!

請注意constexpr被放置在::value跟test的前面,所以我們明確運用了現代C++表達方式


更多範例

指標轉換 :

Andre Weissflog的twitter:
@fenbf 我找到能透過pointee-type來使smart指標進行有用的靜態轉換template 定義式雖然醜陋,但它能順利執行篩選任務:
https://github.com/floooh/oryol/blob/master/code/Modules/Core/Ptr.h#L137 

讓我們看看程式碼:
 /// cast to compatible type
template<class U, 
    class=typename std::enable_if<std::is_convertible<T*,U*>::value>::type>
    operator const Ptr<U>&() const 
    {
        return *(const Ptr<U>*)this;
    };

這是 Ptr.h - smart pointer class 裡某一部份的程式碼,這class是在 oryol - Experimental C++11 multi-platform 3D engine 裡面。

它可能很難讀懂,但我們試著理解看看:
最關鍵的部份是 std::is_convertible<T*,U*>(請看std::is_convertible 介紹)。這個組件被裝載到if_enable運算式組件中。基本上,當T*能被轉換到U*時,我們就能獲得有效用的函式重載。否則編譯器將會對此做出報怨。

還有其它的範例嗎? 讓我欣賞下! :)


更新的版本
我假設確定你的編譯器/程式庫有支援void_t,新的程式碼如下:
// default template:
template< class , class = void >
struct has_toString : false_type { };

// specialized as has_member< T , void > or sfinae
template< class T >
struct has_toString< T , void_t<decltype(&T::toString) > > : std::is_same<std::string, decltype(declval<T>().toString())>
{ };

http://melpon.org/wandbox/permlink/ZzSz25GJVaY4cvzw

相當不錯...對吧? :)

它使用了以detection慣用法為基礎的void_t。基本上,在class內無T::toString()時,SFINAE將會啟用且我們用泛化方式來做出結果,也就是default Template(繼承自false_type)。但是當toString()存在於class內時特化(specialized)本的Template將被選上。我們不在乎toString方法的返回型別的話,這檢驗應該算結束了。但這版本我們還能繼續檢驗用來繼承的is_same。就能檢驗出方法的返回型別是否為std::string。然後我們就能以 true_type 或false_type方式來做為結果

總結
再次感謝大家的意見回饋。此文貼上來後,SFINAE/Templates使我更加混淆且對它們更一知半解:) 但仍值得去嘗試了解此機制的背後運作原理

沒有留言:

張貼留言