使用 SFINAE 检测模板化成员函数是否存在

Use SFINAE to detect the existence of a templated member function

本文关键字:成员 函数 是否 存在 SFINAE 检测 使用      更新时间:2023-10-16

我了解到SFINAE可以用来确定类中是否存在成员函数。例如,下面的代码可用于检查类中是否存在方法 hello

struct has_method_hello {
  using yes = char[1];
  using no  = char[2];
  template <typename U, typename C>
  static constexpr yes& test(decltype(&U::hello));
  template <typename>
  static constexpr no& test(...);
  static constexpr bool value = (sizeof(yes) == sizeof(test<T>(nullptr)));
}; 
struct Foo {
  void hello() {}
}
std::cout << has_method_hello <Foo> :: value << std::endl;  // 1

但是,假设hello是模板化的,我该如何修改技巧以使其仍然可以正常运行?

struct Foo {
  template <typename T>
  void hello(T&) {...}
}

从这里:

namespace details {
  template<template<class...>class Z, class, class...>
  struct can_apply:std::false_type{};
  template<template<class...>class Z, class...Ts>
  struct can_apply<Z, std::void_t<Z<Ts...>>, Ts...>:
    std::true_type{};
}
template<template<class...>class Z, class...Ts>
using can_apply=details::can_apply<Z, void, Ts...>;

现在你想知道是否可以调用foo.hello(int&)

我们有hello_r为您提供调用.hello的返回类型:

template<class F, class...Ts>
using hello_r = decltype(std::declval<F>().hello( std::declval<Ts>()... ));

这导致can_hello

template<class F, class...Ts>
using can_hello = can_apply<hello_r, F, Ts...>;

现在

struct Foo {
  template <typename T>
  void hello(T&) {...}
};
int main() {
  std::cout << can_hello<Foo&, int&>::value << 'n';
  std::cout << can_hello<Foo&, char&>::value << 'n';
  std::cout << can_hello<Foo&>::value << 'n';
}

打印 110。

活生生的例子。

首先,向您展示原始代码的缩短版本:

template <typename T>
struct has_method_hello {
  static constexpr auto test(int) -> decltype(std::declval<T&>().hello(), std::true_type());
  static constexpr std::false_type test(...);
  using result_type = decltype(test(0));
  static const bool value = result_type::value;
};
struct Foo {
  void hello() {}
};

现在让它适用于模板参数,很简单,一个例子:

template <typename T>
struct has_method_hello {
  static constexpr auto test(int) -> decltype(std::declval<T&>().hello(std::declval<int&>()), std::true_type());
  static constexpr std::false_type test(...);
  using result_type = decltype(test(0));
  static const bool value = result_type::value;
};
struct Foo {
  template <typename T>
  void hello(T& v) {}
};

请注意,我在这里对int类型进行了硬编码。您也可以将其作为模板的一部分has_method_hello

这可以做到:

// Example program
#include <iostream>
#include <string>
namespace mpl {
template<typename ...>
struct void_type
{
    using type = void;
};
template<typename ...T>
using void_t = typename void_type<T...>::type;
} // namespace mpl
#define CAN_CALL_METHOD(NAME) 
namespace internal { 
template<typename T, typename ...Args> 
using result_of_call_method_##NAME = decltype( 
    std::declval<T>().NAME(std::declval<Args>()...)); 
} 
template<typename T, typename Signature, typename = void> 
struct can_call_method_##NAME: std::false_type 
{}; 
template<typename T, typename ...Args> 
struct can_call_method_##NAME<T, void(Args...), 
    mpl::void_t<internal::result_of_call_method_##NAME<T, Args...>> 
    >: std::true_type 
{}; 
template<typename T, typename R, typename ...Args> 
struct can_call_method_##NAME<T, R(Args...), 
    typename std::enable_if<!std::is_void<R>::value && 
                             std::is_convertible<internal::result_of_call_method_##NAME<T, Args...>, R 
                                                >::value 
                           >::type 
    >: std::true_type 
{}; 
CAN_CALL_METHOD(hello);
struct Foo {
  template <typename T>
  void hello(T&) {}
};
struct Foo1 {
};
int main()
{
  std::cout << std::boolalpha;
  std::cout << can_call_method_hello<Foo, void(int&)>::value << std::endl;
  std::cout << can_call_method_hello<Foo1, void(int&)>::value << std::endl;
}

IdeOne 链接

我希望这应该适用于任何方法:模板化、重载等。