2011-04-30 38 views
13

A(稍微)過時article探索的方式來使用decltype與SFINAE沿檢測是否一種類型的支持特定運營商,如==<檢測操作者支持與decltype/SFINAE

這裏的示例代碼來檢測一個類支持<操作者:

template <class T> 
struct supports_less_than 
{ 
    static auto less_than_test(const T* t) -> decltype(*t < *t, char(0)) 
    { } 

    static std::array<char, 2> less_than_test(...) { } 

    static const bool value = (sizeof(less_than_test((T*)0)) == 1); 
}; 

int main() 
{ 
    std::cout << std::boolalpha << supports_less_than<std::string>::value << endl; 
} 

此輸出true,由於當然std::string支持<運算符。但是,如果我嘗試了一類使用而支持<運營商,我得到一個編譯錯誤:

error: no match for ‘operator<’ in ‘* t < * t’ 

所以SFINAE不能在這裏工作。我在GCC 4.4和GCC 4.6上試了這個,並且都表現出相同的行爲。那麼,是否有可能以這種方式使用SFINAE來檢測某種類型是否支持某些表達式?

+0

我們不需要用'C++ 0x'來檢查類中是否存在operator <'函數。我們可以簡單地爲泛型重載模擬該函數,並將其大小用於負邏輯。請參閱下面的答案。 – iammilind 2011-04-30 05:12:56

+0

對於那些尋求便攜式預包裝解決方案的人,有'template struct has_less:public true_type-or-false_type {};''in'#include '。文檔:http://www.boost.org/doc/libs/1_56_0/libs/type_traits/doc/html/boost_typetraits/reference/has_less。html – alfC 2014-10-27 23:11:05

回答

9

你需要讓你的less_than_test功能的模板,因爲SFINAE代表換人失敗不是一種錯誤,而且也沒有能在你的代碼無法選擇模板功能。

template <class T> 
struct supports_less_than 
{ 
    template <class U> 
    static auto less_than_test(const U* u) -> decltype(*u < *u, char(0)) 
    { } 

    static std::array<char, 2> less_than_test(...) { } 

    static const bool value = (sizeof(less_than_test((T*)0)) == 1); 
}; 

int main() 
{ 
    std::cout << std::boolalpha << supports_less_than<std::string>::value << endl; 
} 
+0

但是如果一個類沒有'operator <',這個代碼仍然會拋出錯誤。提問者剛剛提到了編譯代碼。我認爲你已經粘貼了相同的內容。 – iammilind 2011-04-30 05:19:25

+0

我發佈了一個更正和完整版本的代碼(因爲我厭惡編輯你的文章如此徹底)。你可以用它來糾正你的例子,在這種情況下,我會刪除我的答案。 – 2011-04-30 10:07:49

+0

@iammilind:看到我的答案,它確實有效,在http://ideone.com/a68WO上的ideone上進行了測試 – 2011-04-30 10:08:15

3

下面簡單的代碼,滿足您的要求(如果你不想編譯錯誤):

namespace supports { 
    template<typename T> // used if T doesn't have "operator <" associated 
    const long long operator < (const T&, const T&); 

    template <class T> 
    struct less_than { 
    T t; 
    static const bool value = (sizeof(t < t) != sizeof(long long)); 
    }; 
} 

用法:

supports::less_than<std::string>::value ====> true; // ok 
supports::less_than<Other>::value ====> false; // ok: no error 

[注:如果你想編譯類錯誤沒有比operator <這是非常容易的代碼非常幾行產生。]

+3

從技術上講,如果一個類型重載'operator <'並且產生一個與'long long'大小相同的類型[或者sizeof(bool)== sizeof(long long)'],那麼這會產生一個錯誤的否定。在這種情況下,無論如何,過載可能不會被用作比較器,但其值得注意。 – 2011-04-30 06:55:44

+0

這也要求一個類型是默認可構造的。這些不僅是值得注意的問題,它們也是完全無足輕重的修復... – ildjarn 2011-04-30 17:32:37

+0

@ildjarn,爲什麼投票,我檢查了我的代碼。即使類型不是默認可構造的,它也可以工作。你能指出這個問題嗎? – iammilind 2011-05-01 05:36:10

0

@xDD確實是正確的,雖然他的例子有些錯誤。

本彙編了ideone:

true 
false 

見行動it here

#include <array> 
#include <iostream> 

struct Support {}; bool operator<(Support,Support) { return false; } 
struct DoesNotSupport{}; 

template <class T> 
struct supports_less_than 
{ 
    template <typename U> 
    static auto less_than_test(const U* u) -> decltype(*u < *u, char(0)) 
    { } 

    static std::array<char, 2> less_than_test(...) { } 

    static const bool value = (sizeof(less_than_test((T*)0)) == 1); 
}; 

int main() 
{ 
    std::cout << std::boolalpha << supports_less_than<Support>::value << std::endl; 
    std::cout << std::boolalpha << 
    supports_less_than<DoesNotSupport>::value << std::endl; 
} 

和結果。

的一點是,SFINAE僅應用於模板功能

+0

@Matthieu,即使我的答案也適用。但OP已決定接受第一個答案。 :) – iammilind 2011-04-30 15:02:18

+1

@iammilind:你的工作副作用,因爲它依賴於'operator <'返回類型的特定大小。我承認它應該適用於所有實際情況:) – 2011-04-30 15:37:38

+0

我在答案中看不到明顯不同的代碼。你的意思是添加的包含指令?如果是這樣的話,我把這些留給了他們,因爲他們在這個問題上被遺漏了。 – xDD 2011-04-30 22:10:29

6

這是C++ 0x中,我們不需要sizeof爲基礎的技巧更多...; - ]

#include <type_traits> 
#include <utility> 

namespace supports 
{ 
    namespace details 
    { 
     struct return_t { }; 
    } 

    template<typename T> 
    details::return_t operator <(T const&, T const&); 

    template<typename T> 
    struct less_than : std::integral_constant< 
     bool, 
     !std::is_same< 
      decltype(std::declval<T const&>() < std::declval<T const&>()), 
      details::return_t 
     >::value 
    > { }; 
} 

(這是基於iammilind的回答,但不要求Toperator<返回類型是不同的大小比long long和不需要T是默認施工的。)

+0

僅供參考,還有'std :: declval ()',你可以使用它來代替你自己的'details:make ()'。 – 2011-08-31 15:54:10

+0

@pyrtsa:啊,的確 - 我傾向於忘記「declval」,因爲它不在VC++ 2010中。我會編輯它。 – ildjarn 2011-08-31 18:40:02

+0

@ldjarn:對於我們這些使用VC++ 2010的人,您是否介意回收細節:: make ()版本? – ForeverLearning 2011-09-14 13:23:07

14

在C++ 11我發現最短,最一般的解決方案是這一個:

#include <type_traits> 

template<class T, class = decltype(std::declval<T>() < std::declval<T>())> 
std::true_type supports_less_than_test(const T&); 
std::false_type supports_less_than_test(...); 

template<class T> using supports_less_than = decltype(supports_less_than_test(std::declval<T>())); 

#include<iostream> 
struct random_type{}; 
int main(){ 
    std::cout << supports_less_than<double>::value << std::endl; // prints '1' 
    std::cout << supports_less_than<int>::value << std::endl; // prints '1' 
    std::cout << supports_less_than<random_type>::value << std::endl; // prints '0' 
} 

作品與g++ 4.8.1clang++ 3.3


任意操作者A更通用的解決方案(UPDATE 2014)

有一個更通用的解決方案,利用所有buil t-in操作符也可以通過STD操作符包裝(如std::less(二進制)或std::negate(一元))訪問(並且可以專用)。

template<class F, class... T, typename = decltype(std::declval<F>()(std::declval<T>()...))> 
std::true_type supports_test(const F&, const T&...); 
std::false_type supports_test(...); 

template<class> struct supports; 
template<class F, class... T> struct supports<F(T...)> 
: decltype(supports_test(std::declval<F>(), std::declval<T>()...)){}; 

這可以以相當普遍的方式來使用,特別是在C++ 14,其中型扣被延遲給操作者包裝呼叫(「透明運營商」)。

對於二進制運算符它可以用作:

#include<iostream> 
struct random_type{}; 
int main(){ 
    std::cout << supports<std::less<>(double, double)>::value << std::endl; // '1' 
    std::cout << supports<std::less<>(int, int)>::value << std::endl; // '1' 
    std::cout << supports<std::less<>(random_type, random_type)>::value << std::endl; // '0' 
} 

對於一元運算符:

#include<iostream> 
struct random_type{}; 
int main(){ 
    std::cout << supports<std::negate<>(double)>::value << std::endl; // '1' 
    std::cout << supports<std::negate<>(int)>::value << std::endl; // '1' 
    std::cout << supports<std::negate<>(random_type)>::value << std::endl; // '0' 
} 

(用C++ 11標準庫是有點複雜,因爲沒有失敗即使沒有爲random_type定義操作,也可以實現decltype(std::less<random_type>()(...)),但是可以在C++ 11中實現手動透明運算符,這些運算符在C++ 14中是標準的)

語法非常流暢。我希望標準中採用這樣的東西。


兩個擴展:

1)它的工作原理,以檢測原始功能的應用:

struct random_type{}; 
random_type fun(random_type x){return x;} 
int main(){ 
    std::cout << supports<decltype(&fun)(double)>::value << std::endl; // '0' 
    std::cout << supports<decltype(&fun)(int)>::value << std::endl; // '0' 
    std::cout << supports<decltype(&fun)(random_type)>::value << std::endl; // '1' 
} 

2)它可以另外地檢測如果結果是可兌換/可比到一定類型,在這種情況下,支持double < double,但由於結果不是指定的,因此將返回編譯時false。

std::cout << supports<std::equal_to<>(std::result_of<std::less<>(double, double)>::type, random_type)>::value << std::endl; // '0' 

注:我只是試圖編譯C++ 14的代碼http://melpon.org/wandbox/並沒有奏效。我認爲在該實現中(clang ++ 3.5 C++ 14)透明運算符(如std::less<>)存在問題,因爲當我實現自己的less<>並自動扣除時,它運行良好。

+0

+1:這是我個人的最愛。它很短而且運作良好。請注意,如果你在Linux上,'C++'可執行文件實際上是'g ++'的別名。 – refi64 2014-10-18 22:00:26

+0

有任何機會將此應用於成員訪問'operator->'? – Walter 2017-06-09 16:19:29