【问题标题】:Detect operator support with decltype/SFINAE使用 decltype/SFINAE 检测操作员支持
【发布时间】:2011-08-15 21:52:09
【问题描述】:

一个(有点)过时的article 探索使用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 当然支持&lt; 运算符。但是,如果我尝试将它与 支持 &lt; 运算符的类一起使用,我会收到编译器错误:

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

所以 SFINAE 在这里不起作用。我在 GCC 4.4 和 GCC 4.6 上试过这个,都表现出相同的行为。那么,是否可以通过这种方式使用 SFINAE 来检测一个类型是否支持某些表达式呢?

【问题讨论】:

  • 我们不需要使用c++0x 来检查类中是否存在operator &lt; 函数。我们可以简单地将该函数模板化为通用重载,并将其大小用于负逻辑。请参阅下面的答案。
  • 对于那些寻求便携式预打包解决方案的人,#include&lt;boost/type_traits/has_less.hpp&gt; 中有 template &lt;class Lhs, class Rhs=Lhs, class Ret=dont_care&gt; struct has_less : public true_type-or-false_type {};。文档:boost.org/doc/libs/1_56_0/libs/type_traits/doc/html/…

标签: c++ c++11 sfinae decltype


【解决方案1】:

在 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(二进制)或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&lt;random_type&gt;()(...))也不会失败,可以在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 &lt; 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 编译代码,但没有成功。我认为该实现(clang++ 3.5 c++14)中的透明运算符(如std::less&lt;&gt;)存在问题,因为当我使用自动推导实现自己的less&lt;&gt; 时,它运行良好。

【讨论】:

  • +1:这是我个人的最爱。它很短而且效果很好。请注意,c++ 可执行文件实际上是g++ 的别名,如果您使用的是 Linux。
  • 有没有机会将此应用于会员访问operator-&gt;
  • @Walter,你是什么意思? template&lt;class T, class = decltype(std::declval&lt;T&gt;()-&gt;() )&gt; std::true_type supports_arrow_test(const T&amp;); std::false_type supports_arrow_test(...);
【解决方案2】:

您需要将 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;
}

【讨论】:

  • 但是如果一个类没有operator &lt;,这段代码仍然会抛出错误。提问者刚刚提到的只是编译代码。而且我认为您粘贴了相同的内容。
  • 我在下面发布了一个更正完整的代码版本(因为我不愿意如此彻底地编辑你的帖子)。您可以使用它来纠正您的示例,在这种情况下,我将删除我的答案。
  • @iammilind:看看我的答案,它确实有效,正如在ideone.com/a68WOideone 上测试的那样
  • 非常好,没有复杂的代码(另外,终于有人演示了一个有用逗号运算符的无废话应用程序!)。有趣的是,它似乎适用于所有内置类型和大多数标准库,以及所有全局 operator&lt;(T,T)。但是,对于课堂上的operator&lt;(T),它似乎不起作用,报告false(使用gcc 4.6)。
【解决方案3】:

这是 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&lt; 返回类型与 long long 的大小不同,并且不要求 T 是默认可构造的。 )

【讨论】:

  • 仅供参考,您还可以使用 std::declval&lt;T&gt;() 代替您自己的 details::make&lt;T&gt;()
  • @pyrtsa :啊,确实——我倾向于忘记declval,因为它不在 VC++ 2010 中。我会编辑。
  • @ldjarn:对于我们这些使用 VC++ 2010 的人,你介意把 details::make() 版本也放回去吗?
  • @Dilip :您应该能够查看我的答案的编辑历史并查看原始代码。
【解决方案4】:

以下简单代码满足您的要求(如果您不想编译错误):

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 &lt;的类编译错误,那么用很少的代码行就很容易生成。]

【讨论】:

  • 从技术上讲,如果一个类型重载 operator &lt; 并且恰好产生与 long long 相同大小的类型 [或者如果 sizeof(bool) == sizeof(long long)],这可能会产生误报。在这种情况下,可能无论如何都不会将重载用作比较器,但值得注意。
  • 这也要求类型是默认可构造的。这些不仅是值得关注的问题,而且解决起来也很简单……
  • @ildjarn,为什么要投反对票,我检查了我的代码。即使类型不是默认可构造的,它也可以工作。能指出问题吗?
  • 我不知道你用的是什么编译器,但是没有编译器应该允许static成员初始化使用非static数据成员——它怎么知道哪个实例的数据成员采用?该代码显然是无意义的。
  • @ildjarn:这是一个未评估的上下文,所以它不需要知道什么实例,只需要知道类型。 stackoverflow.com/q/12508771/103167
【解决方案5】:

@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

查看it here 的实际操作。

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

【讨论】:

  • @Matthieu,即使我的回答也能正常工作。但是OP已决定接受第一个答案。 :)
  • @iammilind:你的工作是有副作用的,因为它依赖于 operator&lt; 的返回类型的特定大小。我承认它应该适用于所有实际情况:)
  • 我没有看到与我的答案中的代码明显不同。您是指添加的包含指令吗?如果是这样,我将它们排除在外,因为它们也被排除在问题之外。
  • @xDD:如果我没记错的话,有两件事:endl 缺少std:: 限定符,并且在展示特征谓词以显示两种选择时非常棒。显然,第二个不会阻止编译。
  • 我只是按照他的要求更正了 OP 的代码。这可能只是一个更大文件的 sn-p 并解释了缺少的包含,他可能正在使用 std 命名空间。
猜你喜欢
  • 1970-01-01
  • 2017-12-22
  • 2013-09-05
  • 1970-01-01
  • 2013-02-20
  • 1970-01-01
  • 2013-07-10
相关资源
最近更新 更多