2011-08-07 34 views
99

我想要一個簡單的例子來了解如何使用std::enable_if。在閱讀this answer之後,我認爲應該不難想出一個簡單的例子。我想使用std::enable_if在兩個成員函數中進行選擇,並只允許使用其中的一個。std :: enable_if有條件地編譯一個成員函數

不幸的是,下面的代碼不能用gcc 4.7進行編譯,並且在幾個小時和幾個小時的嘗試之後我問你們我的錯誤是什麼。

#include <utility> 
#include <iostream> 

template< class T > 
class Y { 

    public: 
     template < typename = typename std::enable_if<true>::type > 
     T foo() { 
      return 10; 
     } 
     template < typename = typename std::enable_if<false>::type > 
     T foo() { 
      return 10; 
     } 

}; 


int main() { 
    Y<double> y; 

    std::cout << y.foo() << std::endl; 
} 

GCC報告以下問題:

% LANG=C make CXXFLAGS="-std=c++0x" enable_if 
g++ -std=c++0x enable_if.cpp -o enable_if 
enable_if.cpp:12:65: error: `type' in `struct std::enable_if<false>' does not name a type 
enable_if.cpp:13:15: error: `template<class T> template<class> T Y::foo()' cannot be overloaded 
enable_if.cpp:9:15: error: with `template<class T> template<class> T Y::foo()' 

爲什麼沒有G ++刪除第二個成員函數錯誤的實例?根據該標準,僅當布爾模板參數爲true時才存在std::enable_if< bool, T = void >::type。但爲什麼不g ++認爲這是SFINAE?我認爲重載錯誤消息來自g ++不會刪除第二個成員函數並認爲這應該是一個重載的問題。

+1

我不確定,但我認爲它是以下內容:enable_if基於SFINAE(替代失敗不是錯誤)。但是,這裏沒有任何替換,因爲沒有參數不能用於確定使用哪個超載。你應該使T的「真」和「假」取決於T.(我知道你不想這樣做的簡單例子,但現在可能太簡單了......) – Philipp

+2

我想到了這一點,並試圖使用'std :: is_same < T, int > ::值'和'! std :: is_same < T, int > ::值'給出了相同的結果。 – evnu

回答

78

只有在參數推導模板參數中的替換使構造不合格時,SFINAE纔有效。沒有這樣的替代。

I thought of that too and tried to use std::is_same< T, int >::value and ! std::is_same< T, int >::value which gives the same result.

這是因爲當類模板實例(當你創建其他案件中Y<int>類型的對象恰好),它實例化其所有成員的聲明(不一定是他們的定義/機構!)。其中也包括其成員模板。請注意,T是已知的,!std::is_same< T, int >::value產生錯誤。因此,這將創建一個類Y<int>包含

class Y<int> { 
    public: 
     /* instantiated from 
     template < typename = typename std::enable_if< 
      std::is_same< T, int >::value >::type > 
     T foo() { 
      return 10; 
     } 
     */ 

     template < typename = typename std::enable_if<true>::type > 
     int foo(); 

     /* instantiated from 

     template < typename = typename std::enable_if< 
      ! std::is_same< T, int >::value >::type > 
     T foo() { 
      return 10; 
     } 
     */ 

     template < typename = typename std::enable_if<false>::type > 
     int foo(); 
}; 

std::enable_if<false>::type訪問不存在的類型,因此形成不良的上述聲明。因此你的程序無效。

您需要使成員模板'enable_if取決於成員模板本身的參數。然後聲明是有效的,因爲整個類型仍然是依賴的。當您嘗試調用其中一個參數時,會發生針對其模板參數的參數推導,並且按照預期發生SFINAE。請參閱this question以及如何做到這一點的相應答案。

+11

...只是爲了澄清,以防萬一它有用:當'Y'模板類的實例被實例化時,編譯器實際上不會編譯模板成員函數;然而,編譯器會執行'T'到成員模板DECLARATIONS中的替換,以便這些成員模板可以在以後實例化。此故障點不是SFINAE,因爲SFINAE僅適用於確定用於*重載分辨率*的可能函數集,並且實例化類不是確定用於重載分辨率的一組函數的情況。 (或者我認爲!) –

5

解決這個問題的一種方法,成員函數的專門化就是把專業化放到另一個類中,然後繼承這個類。您可能必須更改繼承順序才能訪問所有其他基礎數據,但此技術確實有效。

template< class T, bool condition> struct FooImpl; 
template<class T> struct FooImpl<T, true> { 
T foo() { return 10; } 
}; 

template<class T> struct FoolImpl<T,false> { 
T foo() { return 5; } 
}; 

template< class T > 
class Y : public FooImpl<T, boost::is_integer<T> > // whatever your test is goes here. 
{ 
public: 
    typedef FooImpl<T, boost::is_integer<T> > inherited; 

    // you will need to use "inherited::" if you want to name any of the 
    // members of those inherited classes. 
}; 

這種技術的缺點是,如果你需要測試很多不同的事情不同的成員函數,你必須做一個類每一個,它鏈的傳承樹。訪問常用數據成員時也是如此。

例:

template<class T, bool condition> class Goo; 
// repeat pattern above. 

template<class T, bool condition> 
class Foo<T, true> : public Goo<T, boost::test<T> > { 
public: 
    typedef Goo<T, boost::test<T> > inherited: 
    // etc. etc. 
}; 
46

我做了這個簡單的例子也可以工作。

#include <iostream> 
#include <type_traits> 

class foo; 
class bar; 

template<class T> 
struct check 
{ 
    template<class Q = T> 
    typename std::enable_if<std::is_same<Q, bar>::value, bool>::type test() 
    { 
     return true; 
    } 

    template<class Q = T> 
    typename std::enable_if<!std::is_same<Q, bar>::value, bool>::type test() 
    { 
     return false; 
    } 
}; 

int main() 
{ 
    check<foo> check_foo; 
    check<bar> check_bar; 
    if (!check_foo.test() && check_bar.test()) 
     std::cout << "It works!" << std::endl; 

    return 0; 
} 

評論如果你想我詳細說明。我認爲代碼或多或少都是不言自明的,但是我又這樣做了,所以我可能會犯錯:)

你可以在行動here看到它。

+2

這不會在VS2012上編譯。 '錯誤C4519:默認模板參數只允許在類模板上。 – PythonNut

+1

這很不幸。我只用gcc測試過它。也許這有助於:http://stackoverflow.com/a/17543296/660982 – jpihl

+1

這當然是這裏最好的答案,正是我所期待的。 – pongba

3

布爾值需要依賴於正在推導的模板參數。因此,一個簡單的方法來解決是使用默認的布爾參數:

template< class T > 
class Y { 

    public: 
     template < bool EnableBool = true, typename = typename std::enable_if<(std::is_same<T, double>::value && EnableBool)>::type > 
     T foo() { 
      return 10; 
     } 

}; 

不過,如果你想重載的成員函數這是不行的。相反,它的最好使用TICK_MEMBER_REQUIRESTick庫:

template< class T > 
class Y { 

    public: 
     TICK_MEMBER_REQUIRES(std::is_same<T, double>::value) 
     T foo() { 
      return 10; 
     } 

     TICK_MEMBER_REQUIRES(!std::is_same<T, double>::value) 
     T foo() { 
      return 10; 
     } 

}; 

您也可以實現自己的成員,需要宏觀像這樣(萬一你不想使用另一個庫):

template<long N> 
struct requires_enum 
{ 
    enum class type 
    { 
     none, 
     all  
    }; 
}; 


#define MEMBER_REQUIRES(...) \ 
typename requires_enum<__LINE__>::type PrivateRequiresEnum ## __LINE__ = requires_enum<__LINE__>::type::none, \ 
class=typename std::enable_if<((PrivateRequiresEnum ## __LINE__ == requires_enum<__LINE__>::type::none) && (__VA_ARGS__))>::type 
+0

這對我來說並不適用。 Maaybe失蹤了?你能否以一種工作形式重寫OP示例? – user1284631

+0

原始示例不適用於重載。我更新了我的答案,你可以通過重載來做到這一點。 –

6

this後:

Default template arguments are not part of the signature of a template

但可以做這樣的事情:

#include <iostream> 

struct Foo { 
    template < class T, 
       class std::enable_if < !std::is_integral<T>::value, int >::type = 0 > 
    void f(const T& value) 
    { 
     std::cout << "Not int" << std::endl; 
    } 

    template<class T, 
      class std::enable_if<std::is_integral<T>::value, int>::type = 0> 
    void f(const T& value) 
    { 
     std::cout << "Int" << std::endl; 
    } 
}; 

int main() 
{ 
    Foo foo; 
    foo.f(1); 
    foo.f(1.1); 

    // Output: 
    // Int 
    // Not int 
} 
+0

它的工作原理,但這基本上是模板功能,而不是類本身...它不允許放棄兩個相同原型函數中的任何一個(當你需要通過重載時)。但是,這個想法很好。你能否以一種工作形式重寫OP示例? – user1284631

+0

'class std :: enable_if .....'應該是'typename std :: enable_if .....' – marcinj

8

對於那些正在尋找 「只是工程」 的解決方案的後來者:

#include <utility> 
#include <iostream> 

template< typename T > 
class Y { 

    template< bool cond, typename U > 
    using resolvedType = typename std::enable_if< cond, U >::type; 

    public: 
     template< typename U = T > 
     resolvedType< true, U > foo() { 
      return 11; 
     } 
     template< typename U = T > 
     resolvedType< false, U > foo() { 
      return 12; 
     } 

}; 


int main() { 
    Y<double> y; 

    std::cout << y.foo() << std::endl; 
} 

編譯:

g++ -std=gnu++14 test.cpp 

運行提供了:

./a.out 
11 
相關問題