2011-04-30 40 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