8

I learned SFINAE can be used to determine whether a member function exists in a class or not. For example, the following code can be used to check if the method hello is present in a class.

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

However, suppose the hello is templated, how can I modify the trick so it can still function properly?

struct Foo {
  template <typename T>
  void hello(T&) {...}
}
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
Jes
  • 2,614
  • 4
  • 25
  • 45

3 Answers3

5

From here:

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

now you want to know if foo.hello(int&) can be called:

We have hello_r that gives you the return type of invoking .hello:

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

which leads to can_hello:

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

now

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

prints 110.

live example.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Elegant. Maybe you could add an example with a function template that also takes a value parameter to show the limits of the trait? – Rerito Jun 01 '16 at 07:59
  • @rerito When I run into that, I make a patch alias that replaces values with `integral_constant` style types. C++ metaprogramming works much better if you treat things as types (sometimes I even do templates as types) – Yakk - Adam Nevraumont Jun 01 '16 at 13:27
  • @Yakk I'm interested in examples showing that technique. And I think it could be a great addition to your answer. – Rerito Jun 02 '16 at 07:51
4

First of all, showing you a shortened version of your original code:

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

Now making it work for a template parameter, easy, an example:

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

Note that, I have hardcoded int type here. You can make that part of has_method_hello template too.

Arunmu
  • 6,837
  • 1
  • 24
  • 46
0

This can be done:

// 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 link

This should work i hope for any method: templated, overloaded etc.

Elohim Meth
  • 1,777
  • 9
  • 13