使用 decltype/SFINAE 检测运算符支持

发布于 2024-11-04 08:36:50 字数 990 浏览 2 评论 0原文

一篇(有些)过时的文章探讨了使用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来检测类型是否支持某些表达式呢?

A (somewhat) outdated article explores ways to use decltype along with SFINAE to detect if a type supports certain operators, such as == or <.

Here's example code to detect if a class supports the < operator:

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

This outputs true, since of course std::string supports the < operator. However, if I try to use it with a class that doesn't support the < operator, I get a compiler error:

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

So SFINAE is not working here. I tried this on GCC 4.4 and GCC 4.6, and both exhibited the same behavior. So, is it possible to use SFINAE in this manner to detect whether a type supports certain expressions?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

感悟人生的甜 2024-11-11 08:36:50

在 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 标准库有点复杂,因为实例化 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 <支持 double,但会返回编译时 false,因为结果不是指定的结果。

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

注意:我只是尝试在 http://melpon.org 中使用 C++14 编译代码/wandbox/ 但它不起作用。我认为该实现(clang++ 3.5 c++14)中的透明运算符(例如 std::less<>)存在问题,因为当我实现自己的 less<> 时;自动扣除效果很好。

In C++11 the shortest most general solution I found was this one:

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

Works with g++ 4.8.1 and clang++ 3.3


A more general solution for arbitrary operators (UPDATE 2014)

There is a more general solution that exploits the fact that all built-in operators are also accessible (and posibly specialized) through STD operator wrappers, such as std::less (binary) or 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>()...)){};

This can be used in a quite general way, especially in C++14, where type deduction is delayed to the operator wrapper call ("transparent operators").

For binary operators it can be used as:

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

For unary operators:

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

(With the C++11 standard library is a bit more complicated because there is no failure on instatiating decltype(std::less<random_type>()(...)) even if there is no operation defined for random_type, one can implement manually transparent operators in C++11, that are standard in C++14)

The syntax is quite smooth. I hope something like this is adopted in the standard.


Two extensions:

1) It works to detect raw-function applications:

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) It can additionally detect if the result is convertible/comparable to a certain type, in this case double < double is supported but a compile-time false will be returned because the result is not the specified one.

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

Note: I just tried to compile the code with C++14 in http://melpon.org/wandbox/ and it didn't work. I think there is a problem with transparent operators (like std::less<>) in that implementation (clang++ 3.5 c++14), since when I implement my own less<> with automatic deduction it works well.

失退 2024-11-11 08:36:50

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

You need to make your less_than_test function a template, since SFINAE stands for Substitution Failure Is Not An Error and there's no template function that can fail selection in your code.

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;
}
墨小沫ゞ 2024-11-11 08:36:50

这是 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 的答案,但不需要 T< /code> 的 operator< 返回类型的大小与 long long 不同,并且不要求 T 可以默认构造。 )

This is C++0x, we don't need sizeof-based tricks any more... ;-]

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

(This is based on iammilind's answer, but doesn't require that T's operator< return-type be a different size than long long and doesn't require that T be default-constructable.)

雨轻弹 2024-11-11 08:36:50

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

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

Below simple code satisfies your requirement (if you don't want compile error):

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

Usage:

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

[Note: If you want compile error for classes not having operator < than it's very easy to generate with very few lines of code.]

ˉ厌 2024-11-11 08:36:50

@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 仅适用于模板函数

@xDD is indeed correct, though his example is slightly erroneous.

This compiles on 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;
}

And results in:

true
false

See it here in action.

The point is that SFINAE only applies to template functions.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文