16

一篇(有点)过时的文章探讨了decltype与 SFINAE 一起使用来检测类型是否支持某些运算符的方法,例如==or <

下面是用于检测类是否支持<运算符的示例代码:

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 来检测一个类型是否支持某些表达式呢?

4

5 回答 5

19

在 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


任意运算符的更通用解决方案(2014 年更新)

有一个更通用的解决方案利用了这样一个事实,即所有内置运算符也可以通过 STD 运算符包装器访问(并且可能是专门的),例如std::less(binary) 或std::negate(unary)。

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 标准库稍微复杂一些,因为decltype(std::less<random_type>()(...))即使没有为 定义操作,实例化也不会失败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是支持的,但由于结果不是指定的,所以会返回编译时错误。

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

注意:我只是尝试在http://melpon.org/wandbox/中使用 C++14 编译代码,但没有成功。我认为std::less<>该实现(clang++ 3.5 c++14)中的透明运算符(如 )存在问题,因为当我less<>使用自动推导实现我自己的时,它运行良好。

于 2013-09-04T00:48:46.303 回答
9

您需要将 less_than_test 函数设为模板,因为 SFINAE 代表 Substitution Failure Is Not An Error,并且没有模板函数会导致代码中的选择失败。

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;
}
于 2011-04-30T04:12:51.943 回答
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 longT默认构造不同。)

于 2011-04-30T17:56:35.047 回答
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 <用很少的代码行很容易生成。]

于 2011-04-30T05:04:11.513 回答
0

@xDD 确实是正确的,尽管他的例子有点错误。

这在 ideone 上编译:

#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;
}

结果:

true
false

在这里查看的实际应用。

关键是 SFINAE 仅适用于模板函数

于 2011-04-30T10:06:45.377 回答