【问题标题】:Other ways of checking if a class has a certain member function检查类是否具有特定成员函数的其他方法
【发布时间】:2016-03-30 21:27:22
【问题描述】:

让我们检查一下

struct Thing {
    int foo(double, bool) {return 0;}
};

在编译时具有int foo(double, bool) 成员函数。有很多方法可以做到这一点,大多数只是其他方法的变体。有人能想出一种与我在这里提到的 5 种方式截然不同(或至少相当有创意)的方式吗?我只是想通过模板和 SFINAE 学习一些新技术。

#include <iostream>
#include <type_traits>

// Using void_t (this includes using std::is_detected).
template <typename T>
using void_t = void;

template <typename T, typename = void>
struct has_foo : std::false_type {};

template <typename T>
struct has_foo<T,
        void_t<decltype(static_cast<int>(std::declval<T>().foo(double{}, bool{})))>
    > : std::true_type {};

// Using the ... default argument.
template <typename T>
struct hasfoo {
    template <typename U>
    static std::true_type test (decltype(static_cast<int(T::*)(double, bool)>(&T::foo))*);  // or 'decltype(static_cast<int>(std::declval<U>().foo(double{}, bool{})))*' works fine too.

    template <typename>
    static std::false_type test (...);

    static constexpr bool value = decltype(test<T>(nullptr))::value;
};

// Overloads and trailing return types.
template <typename>
struct Helper : std::true_type {};

template <typename T>
auto helper(int) -> Helper<decltype(static_cast<int>(std::declval<T>().foo(double{}, bool{})))>;

template <typename>
std::false_type helper(long);

template <typename T>
constexpr bool hasFoo() {return decltype(helper<T>(0))::value;}

// Comma operator (basically the same as the above).
template <typename T>
auto check(int) -> decltype(static_cast<int>(std::declval<T>().foo(double{}, bool{})), std::true_type{});

template <typename T>
std::false_type check(...);

template <typename T>
using HasFoo = decltype(check<T>(0));

// Member function pointer template parameter.
template <typename T>
struct Hasfoo {
    template <typename U, int(U::*)(double, bool)>
    struct Tag;

    template <typename U>
    static constexpr bool test (Tag<U, &U::foo>*) {return true;}

    template <typename>
    static constexpr bool test (...) {return false;}

    static constexpr bool value = test<T>(nullptr);
};

// Tests
struct Thing {
    int foo(double, bool) {return 0;}
};

int main() {
    static_assert (has_foo<Thing>::value, "");
    static_assert (hasfoo<Thing>::value, "");
    static_assert (hasFoo<Thing>(), "");
    static_assert (HasFoo<Thing>::value, "");
}

编辑:我只记得很早以前 Yakk 给出了一个优雅且更通用的解决方案(这是他的实际打字,仅修改以匹配 foo 函数):

namespace meta {
  namespace details {
    template<template<class...>class Z, class=void, class...Ts>
    struct can_apply : std::false_type {};
    template<template<class...>class Z, class...Ts>
    struct can_apply<Z, decltype((void)(std::declval<Z<Ts...>>())), Ts...>:
      std::true_type
    {};
  }
  template<template<class...>class Z, class...Ts>
  using can_apply = details::can_apply<Z,void,Ts...>;
}

template<class T>
using member_foo = decltype(static_cast<int(T::*)(double, bool)>(&T::foo));

template<class T>
using has_member_foo = meta::can_apply<member_foo, T>;

【问题讨论】:

  • 在列出的四个选项中,例如,只有hasfoo 会检测到从int foo(double,bool) { return 0; }int foo(double,double) { return 0; } 的更改。
  • 嗯...我想我不太关心隐式转换。但是decltype(static_cast&lt;int(T::*)(double, bool)&gt;(&amp;T::foo))* 可以用于所有这些。这就是我很好奇的表达方式。
  • template&lt;class T&gt; concept bool foo_double_bool = requires (T t) { t.foo(0.0, true); };。你需要多少种不同的方式来表达同一件事?
  • @T.C.尚未邀请概念参加聚会:-)

标签: c++ templates c++11 sfinae


【解决方案1】:

有人能想出一种与我在这里提到的 5 种方法截然不同(或至少相当有创意)的方法吗?

一种相当有创意的方法可能是下面的方法。
它基于noexcept 运算符和一个简单的using 声明(这里命名为Type)。
剩下的就是 SFINAE 和部分模板专业化。

它遵循一个最小的工作示例:

#include<type_traits>
#include<utility>

template<typename T, bool>
using Type = T;

template<typename T, typename = T>
struct U: std::false_type {};

template<typename T>
struct U<T, Type<T, noexcept(std::declval<T>().f(42))>>: std::true_type {};

struct S { void f(int) {} };
struct T {};

int main() {
    static_assert(U<S>::value, "!");
    static_assert(not U<T>::value, "!");
}

与其他解决方案相比,此解决方案存在问题
例如,下面的结构也可以通过测试:

struct R { int f(double) {} };

换句话说,只要要测试的函数接受一个可以转换为 42 的类型的参数,并且无论返回类型是什么,都可以接受。

【讨论】:

    【解决方案2】:

    有人能想出一种与我在这里提到的 5 种方法截然不同(或至少相当有创意)的方法吗?

    一种相当有创意的方法可能是下面的方法。
    它基于sizeof 运算符和一个简单的using 声明(这里命名为Type)。
    剩下的就是 SFINAE 和部分模板专业化。

    它遵循一个最小的工作示例:

    #include<type_traits>
    #include<cstddef>
    
    template<typename T, std::size_t>
    using Type = T;
    
    template<typename T, typename = T>
    struct U: std::false_type {};
    
    template<typename T>
    struct U<T, Type<T, sizeof(static_cast<void(T::*)(int)>(&T::f))>>: std::true_type {};
    
    struct S { void f(int) {} };
    struct R { int f(double) { return 42; } };
    struct T {};
    
    int main() {
        static_assert(U<S>::value, "!");
        static_assert(not U<R>::value, "!");
        static_assert(not U<T>::value, "!");
    }
    

    【讨论】:

      【解决方案3】:

      有人能想出一种与我在这里提到的 5 种方式截然不同(或至少相当有创意)的方式吗?

      一种相当有创意的方法可能是下面的方法。
      它不会利用任何未评估的上下文。相反,它依赖于一个简单的using 声明(这里命名为Type),仅此而已。
      剩下的就是 SFINAE 和部分模板专业化。

      它遵循一个最小的工作示例:

      #include<type_traits>
      
      template<typename T, void(T::*)(int)>
      using Type = T;
      
      template<typename T, typename = T>
      struct U: std::false_type {};
      
      template<typename T>
      struct U<T, Type<T, &T::f>>: std::true_type {};
      
      struct S { void f(int) {} };
      struct R { int f(double) { return 42; } };
      struct T {};
      
      int main() {
          static_assert(U<S>::value, "!");
          static_assert(not U<R>::value, "!");
          static_assert(not U<T>::value, "!");
      }
      

      【讨论】:

        【解决方案4】:

        有人能想出一种与我在这里提到的 5 种方式截然不同(或至少相当有创意)的方式吗?

        一种相当有创意的方法可能是下面的方法。
        它基于函数模板和重载。如果您知道标签调度的工作原理,那应该很容易理解。

        它遵循一个最小的工作示例:

        #include<type_traits>
        #include<cstddef>
        
        template<typename T, void(T::*)(int) = &T::f>
        constexpr std::true_type f(int) { return {}; }
        
        template<typename T>
        constexpr std::false_type f(char) { return {}; }
        
        template<typename T>
        constexpr auto detect() { return f<T>(0); }
        
        struct S { void f(int) {} };
        struct R { int f(double) { return 42; } };
        struct T {};
        
        int main() {
            static_assert(detect<S>(), "!");
            static_assert(not detect<R>(), "!");
            static_assert(not detect<T>(), "!");
        }
        

        【讨论】:

          【解决方案5】:

          有人能想出一种与我在这里提到的 5 种方式截然不同(或至少相当有创意)的方式吗?

          如果你可以使用 C++14,另一种检查类是否有成员变量的方法是使用模板变量。
          举个例子:

          template<typename T, typename = void>
          constexpr bool has_foo = false;
          
          template<typename T>
          constexpr bool has_foo<T, decltype(std::declval<T>().foo(), void())> = true;
          

          我想它可能至少是更紧凑的解决方案之一。

          它遵循一个最小的工作示例:

          #include<utility>
          
          template<typename T, typename = void>
          constexpr bool has_f = false;
          
          template<typename T>
          constexpr bool has_f<T, decltype(std::declval<T>().f(0), void())> = true;
          
          struct S { void f(int) {} };
          struct T {};
          
          int main() {
              static_assert(has_f<S>, "!");
              static_assert(not has_f<T>, "!");
          }
          

          【讨论】:

            猜你喜欢
            • 2017-06-15
            • 2020-07-08
            • 1970-01-01
            • 2022-12-12
            • 1970-01-01
            • 2020-10-12
            • 2015-08-03
            • 1970-01-01
            • 2014-12-09
            相关资源
            最近更新 更多