2013-07-29 85 views
3

我想寫一個模板類,它可能會或可能不會定義一個特定的成員函數,具體取決於它的模板參數類型。此外,此成員函數的返回類型取決於模板參數成員的返回類型(如果已定義)。SFINAE選擇性地包括成員

下面是我的代碼小例子

#include <iostream> 
#include <type_traits> 

template <typename T> 
struct has_foo_int { 
private: 
    template <typename U> 
    static decltype(std::declval<U>().foo(0), void(), std::true_type()) test(int); 
    template <typename> 
    static std::false_type test(...); 
public: 
    typedef decltype(test<T>(0)) test_type; 
    enum { value = test_type::value }; 
}; 

template <typename T, bool HasFooInt> 
struct foo_int_return_type; 

template<typename T> 
struct foo_int_return_type<T,false> {}; 

template<typename T> 
struct foo_int_return_type<T,true> { 
    using type = decltype(std::declval<T>().foo(0)); 
}; 

template<typename T> 
struct mystruct 
{ 
    T val; 

    //auto someMethod(int i) -> decltype(std::declval<T>().foo(0)) // error: request for member ‘foo’ in ‘std::declval<double>()’, which is of non-class type ‘double’ 
    //auto someMethod(int i) -> typename foo_int_return_type<T,has_foo_int<T>::value>::type // error: no type named ‘type’ in ‘struct foo_int_return_type<double, false>’ 
    template<typename R=typename foo_int_return_type<T,has_foo_int<T>::value>::type> R someMethod(int i) // error: no type named ‘type’ in ‘struct foo_int_return_type<double, false>’ 
    { 
     return val.foo(i); 
    } 
}; 

struct with_foo_int { 
    int foo(int i){ 
     return i+1; 
    } 
}; 

using namespace std; 

int main(void) 
{ 
    mystruct<with_foo_int> ms1; 
    cout << ms1.someMethod(41) << endl; 

    mystruct<double> ms2; 

    return 0; 
} 

我想發生的是,代碼編譯罰款和輸出42 ms1.someFunc(41)。我還希望如果不小心試圖撥打 ms2它將無法編譯。

不幸的是,我試過的每個選擇都失敗了。第一和第二,我想我明白他們爲什麼不行。

我讀here SFINAE只適用於模板函數,所以我試着給出一個虛擬模板參數來計算返回類型,但是這也以相同的方式失敗。

我很清楚這裏沒有理解的東西,我錯過了什麼?是否有可能實現我想要做的事情?

謝謝。

P.s.我使用的是G ++ 4.7.3

P.p.s我也曾嘗試std::enable_if但得到大致相同的結果與我的foo_int_return_type結構。

+1

該函數本身需要將相關參數作爲模板參數,否則在調用點沒有替換,並且您沒有獲得SFINAE。一個流行的解決方法是像'template ' – Xeo

+0

它可行!我不確定爲什麼,但如果您將此作爲答案並解釋,我會很樂意接受。 – Dan

+0

對於SFINAE,編譯器(或用戶)需要推導(提供)編譯器稍後將嘗試的類型** S ** ubstitute和** F ** ail –

回答

2

下面是一個簡短,整潔和記錄的方式做你正在嘗試, 以及一些可能的錯誤之後解決。

#include <type_traits> 

/* 
    Template `has_mf_foo_accepts_int_returns_int<T>` 
    has a static boolean public member `value` that == true 
    if and only if `T` is a class type that has a public 
    member function or member function overload 
    `int T::foo(ArgType) [const]` where `ArgType` 
    is a type to which `int` is implicitly convertible. 
*/ 
template <typename T> 
struct has_mf_foo_accepts_int_returns_int { 

    /* SFINAE success: 
     We know now here `int *` is convertible to 
     "pointer to return-type of T::foo(0)" 
    */ 
    template<typename A> 
    static constexpr bool test(
     decltype(std::declval<A>().foo(0)) *prt) { 
     /* Yes, but is the return-type of `T::foo(0)` 
      actually *the same* as `int`?... 
     */ 
     return std::is_same<int *,decltype(prt)>::value; 
    } 

    // SFINAE failure :(
    template <typename A> 
    static constexpr bool test(...) { 
     return false; 
    } 

    /* SFINAE probe. 
     Can we convert `(int *)nullptr to 
     "pointer to the return type of T::foo(0)"? 
    */ 
    static const bool value = test<T>(static_cast<int *>(nullptr)); 
}; 

template<typename T> 
struct mystruct 
{ 
    using has_good_foo = has_mf_foo_accepts_int_returns_int<T>; 

    T val; 

    /* SFINAE: 
     `template<typename R> R someMethod(R)` will be this if and only 
     if `R` == `int` and `has_good_foo` == true.   
    */ 
    template<typename R = int> 
    typename std::enable_if< 
     (has_good_foo::value && std::is_same<R,int>::value),R 
    >::type 
    someMethod(R i) { 
     return val.foo(i); 
    } 

    /* SFINAE: 
     `template<typename R> R someMethod(R)` will be this if and only 
     if `R` != `int` or `has_good_foo` != true.  
    */ 
    template<typename R = int> 
    typename std::enable_if< 
     !(has_good_foo::value && std::is_same<R,int>::value),R 
    >::type 
    someMethod(R i) { 
     static_assert(has_good_foo::value && std::is_same<R,int>::value, 
      "mystruct<T> does not implement someMethod(R)"); 
     return i; 
    } 
}; 

// Testing... 

#include <iostream> 

struct with_foo_int 
{ 
    int foo(int i) { 
     return i + 1; 
    } 
}; 

using namespace std; 

int main(void) 
{ 
    mystruct<with_foo_int> ms1; 
    cout << ms1.someMethod(41) << endl; 

    mystruct<double> ms2; 
    cout << ms2.someMethod(41) << endl; // static_assert failure 

    return 0; 
} 

該解決方案忠實地再現了幾個可能的漏洞,你 自己嘗試張貼: -

1)它看起來好像你可能會認爲評估std::declval<U>().foo(0)是 確定的SFINAE方式是否存在U::foo並且採用int類型的單個參數 。它沒有。它僅僅是一種確定 U::foo(ArgType)是否存在的SFINAE方式,其中ArgType0是 可以隱式轉換的任何東西。因此ArgType可以是任何指針或算術 類型,而不僅僅是int

2)您可能沒有考慮到std::declval<U>().foo(0)會滿意 如果或任兩者U::foo(ArgType)U::foo(ArgType) const存在。您 可能會在意您是否撥打constU的非const成員函數,並且您肯定會關心您調用的兩個成員函數中的哪一個。如果 with_foo_int被定義爲:

struct with_foo_int 
{ 
    int foo(int i) const { 
     return i + 1; 
    } 
    int foo(int i) { 
     return i + 2; 
    } 
}; 

然後給出的解決方案將調用非const過載和 ms1.someMethod(41)將== 43

2)很容易處理。如果您希望確保只能撥打 T::foo(ArgType) const,請將const限定符添加到mystruct::someMethod。 如果您不在意或只希望撥打T::foo(ArgType),請保留 原樣。

1)是有點難以解決,因爲你必須製作一個SNIFAE探頭 T::foo這是唯一滿意的,如果它有正確的簽名,那 簽名要麼是const合格與否。假設你想要 int T::foo(int) const。在這種情況下,更換模板 has_mf_foo_accepts_int_returns_int

/* Template `has_mf_foo_arg_int_returns_int<T> 
    has a static boolean public member `value` that == true 
    if and only if `T` is a class type that has an un-overloaded 
    a public member `int T::foo(int) const`. 
*/ 
template< typename T> 
struct has_mf_foo_arg_int_returns_int 
{ 
    /* SFINAE foo-has-correct-sig :) */ 
    template<typename A> 
    static std::true_type test(int (A::*)(int) const) { 
     return std::true_type(); 
    } 

    /* SFINAE foo-exists :) */ 
    template <typename A> 
    static decltype(test(&A::foo)) 
    test(decltype(&A::foo),void *) { 
     /* foo exists. What about sig? */ 
     typedef decltype(test(&A::foo)) return_type; 
     return return_type(); 
    } 

    /* SFINAE game over :(*/ 
    template<typename A> 
    static std::false_type test(...) { 
     return std::false_type(); 
    } 

    /* This will be either `std::true_type` or `std::false_type` */ 
    typedef decltype(test<T>(0,0)) type; 

    static const bool value = type::value; /* Which is it? */ 
}; 

,並在模板mystruct取代:

using has_good_foo = has_mf_foo_accepts_int_returns_int<T>; 

有:

using has_good_foo = has_mf_foo_arg_int_returns_int<T>; 

(模板has_mf_foo_arg_int_returns_int適用 從my other answer和 你可以閱讀它的工作原理。)

你從SFINAE精度中獲得的後一種方法的價格是 。該方法要求您嘗試取T::foo, 的地址以查看它是否存在。但C++不會給你超載的成員函數的地址,所以如果T::foo被重載,這種方法將失敗。

此處的代碼將編譯(或適當地static_assert)與 GCC> = 4.7.2 clang> = 3.2。