2012-07-04 54 views
8

我有一個SFINAE問題:SFINAE:編譯器不挑專業的模板類

在下面的代碼,我想C++編譯器挑專門仿函數和打印「特殊」,但它打印「一般「而是。

#include <iostream> 
#include <vector> 

template<class T, class V = void> 
struct Functor { 
    void operator()() const { 
    std::cerr << "general" << std::endl; 
    } 
}; 

template<class T> 
struct Functor<T, typename T::Vec> { 
    void operator()() const { 
    std::cerr << "special" << std::endl; 
    } 
}; 

struct Foo { 
    typedef std::vector<int> Vec; 
}; 

int main() { 
    Functor<Foo> ac; 
    ac(); 
} 

我該如何修復它,以便自動使用專門的結構?注意我不想直接專門化Foo上的Functor結構,但我想專門針對所有類型爲Vec的類型。

P.S:我使用的G ++ 4.4.4

+0

刪除了'compiler'標籤,它通常用於編譯過程本身的問題,而這個問題是關於C++的語言。 –

回答

11

對不起,誤導您在最近的答案,我想了一會兒,它會更簡單。所以我會盡力在這裏提供一個完整的解決方案。一般的方法來解決這一類的問題是寫一個特質幫手模板,並與enable_if一起使用(無論是C++ 11,升壓或手動執行)來決定的一類專業化:

特質

一個簡單的方法,不一定是最好的,但簡單的寫的是:

template <typename T> 
struct has_nested_Vec { 
    typedef char yes; 
    typedef char (&no)[2]; 
    template <typename U> 
    static yes test(typename U::Vec* p); 
    template <typename U> 
    static no test(...); 

    static const bool value = sizeof(test<T>(0)) == sizeof(yes); 
}; 

方法很簡單,提供兩個模板功能,即返回類型不同的尺寸。其中一個採用嵌套Vec類型,另一個採用省略號。對於嵌套Vec的所有類型,第一個重載是更好的匹配(省略號是任何類型的最差匹配)。對於那些沒有嵌套的類型Vec SFINAE將丟棄該過載,剩下的唯一選項將是省略號。所以現在我們有一個特徵來問是否有嵌套的Vec類型。

啓用如果

你可以使用這個從任何庫,或者你可以滾你自己,這是很簡單的:

template <bool state, typename T = void> 
struct enable_if {}; 

template <typename T> 
struct enable_if<true,T> { 
    typedef T type; 
}; 

當第一個參數是false,基本模板唯一的選擇,並且沒有嵌套的type,如果條件是true,那麼enable_if有嵌套的type,我們可以使用SFINAE。

實施

現在,我們需要提供的模板,將使用SFINAE只有那些類型的嵌套Vec專業化:

template<class T, class V = void> 
struct Functor { 
    void operator()() const { 
     std::cerr << "general" << std::endl; 
    } 
}; 
template<class T> 
struct Functor<T, typename enable_if<has_nested_Vec<T>::value>::type > { 
    void operator()() const { 
     std::cerr << "special" << std::endl; 
    } 
}; 

每當我們實例Functor一個類型,編譯器會嘗試使用專門化,它將依次實例化has_nested_Vec並獲得一個真值,傳遞給enable_if。對於那些值爲falseenable_if的類型沒有嵌套的type類型,所以在SFINAE中專用化將被丟棄,並且將使用基本模板。

你的具體情況

你的具體情況,在那裏看來你真的不需要專門整個類型只是運營商,你可以混合的三個要素到一個單一的一種: Functor一個分派到的基礎上,Vec存在兩個內部模板的功能之一,除去enable_if的需求和特性類:

template <typename T> 
class Functor { 
    template <typename U> 
    void op_impl(typename U::Vec* p) const { 
     std::cout << "specialized"; 
    } 
    template <typename U> 
    void op_impl(...) const { 
     std::cout << "general"; 
    } 
public: 
    void operator()() const { 
     op_impl<T>(0); 
    } 
}; 
2

儘管這是一個老問題,我認爲它還是值得提供一對夫婦更快速修復原始代碼的更多選擇。

基本上,問題不在於使用SFINAE(那部分是細,實際上),但具有默認參數的在主模板(void)在偏特提供的參數的匹配(typename T::Vec) 。由於主模板中的默認參數,Functor<Foo>實際上表示Functor<Foo, void>。當編譯器嘗試使用特化實例化時,它會嘗試將兩個參數與特化中的參數匹配並失敗,因爲void不能替代std::vector<int>。然後它回退到使用主模板進行實例化。

因此,最快的修正,即假設所有的Vec s爲std::vector<int> S,與此

template<class T, class E = std::vector<int>> 

專業化現在將被用來替換線

template<class T, class V = void> 

,因爲參數將匹配。簡單但太限制。顯然,我們需要更好地控制專業化中參數的類型,以便使它與我們可以在主模板中指定爲默認參數的東西匹配。不需要定義新的特點一個快速的解決方案是這樣的:

#include <iostream> 
#include <vector> 
#include <type_traits> 

template<class T, class E = std::true_type> 
struct Functor { 
    void operator()() const { 
    std::cerr << "general" << std::endl; 
    } 
}; 

template<class T> 
struct Functor<T, typename std::is_reference<typename T::Vec&>::type> { 
    void operator()() const { 
    std::cerr << "special" << std::endl; 
    } 
}; 

struct Foo { 
    typedef std::vector<int> Vec; 
}; 

int main() { 
    Functor<Foo> ac; 
    ac(); 
} 

這適用於任何類型的Vec可能有意義,這裏包括基本類型和數組,例如,和引用或指針到他們的工作。

1

檢測成員類型是否存在的另一種方法是使用void_t。由於有效的部分特化優於普通實現,只要它們與默認參數匹配,我們就需要一種在有效時計算爲void的類型,並且僅當指定的成員存在時纔有效;這種類型通常是(並且正如C++ 17,正則的),被稱爲void_t

template<class...> 
using void_t = void; 

如果你的編譯器不能正確支持它(在早期的C++編譯器14,別名模板未使用的參數得不到保障,以確保SFINAE,打破了上述void_t),一個可行的解決方法。

template<typename... Ts> struct make_void { typedef void type; }; 
template<typename... Ts> using void_t = typename make_void<Ts...>::type; 

作爲C++ 17,void_t是在公用事業庫可用,在type_traits

#include <iostream> 
#include <vector> 
#include <type_traits> // For void_t. 

template<class T, class V = void> 
struct Functor { 
    void operator()() const { 
    std::cerr << "general" << std::endl; 
    } 
}; 

// Use void_t here. 
template<class T> 
struct Functor<T, std::void_t<typename T::Vec>> { 
    void operator()() const { 
    std::cerr << "special" << std::endl; 
    } 
}; 

struct Foo { 
    typedef std::vector<int> Vec; 
}; 

int main() { 
    Functor<Foo> ac; 
    ac(); 
} 

由此,輸出爲special,如預期的那樣。


在這種情況下,因爲我們檢查的成員類型的存在,這個過程是非常簡單的;它可以不用表達式SFINAE或type_traits庫,允許我們重寫檢查以在必要時使用C++ 03工具。

// void_t: 
// Place above Functor's definition. 
template<typename T> struct void_t { typedef void type; }; 

// ... 

template<class T> 
struct Functor<T, typename void_t<typename T::Vec>::type> { 
    void operator()() const { 
    std::cerr << "special" << std::endl; 
    } 
}; 

據我所知,這應該對大多數工作,如果不是全部,SFINAE能力的C++ 03-,C++ 11-,C++ 14-,或1Z符合標準的C++編譯器。在處理稍微落後於標準的編譯器時,或編譯尚未具有C++ 11兼容編譯器的平臺時,這會很有用。


有關void_t的更多信息,請參閱cppreference