3

I am using SFINAE idiom to check if a type has a method (some_method()) with a given signature defined:

template <typename... Other>
struct has_method {
    static constexpr bool value = has_method<Other...>::value;
};

template <typename U, typename... Other>
struct has_method<U, Other...> {
    static constexpr bool value =
        has_method<U>::value && has_method<Other...>::value;
};

template <typename U>
struct has_method<U> {
    template <typename T, T>
    struct helper;
    template <typename T>
    static std::uint8_t check(helper<int (*)(size_t), &T::some_method>*);
    template <typename T>
    static std::uint16_t check(...);

    static constexpr bool value = sizeof(check<U>(0)) == sizeof(std::uint8_t);
};

I would like to modify it to check if the return type of the function is arithmetic type, using std::is_arithmetic. Would that be possible? I have tried modifying it using static_assert() together with std::is_arithmetic< decltype(((T*)nullptr)->foo())>::value but didn't succeed.

syntagma
  • 23,346
  • 16
  • 78
  • 134
  • I know you can check if a method exists without knowing a return type be using `decltype` on a call to the function using `declval`. Then you can pass the complete signature to something like the above, but extend it to sfinae if the return type isn't arithmetic. – Weak to Enuma Elish Dec 02 '15 at 16:18

3 Answers3

1

I think this is what you are looking for:

struct Unknown {};

template <typename T_>
struct Wrap
{
    typedef T_ type;
};

template <class C_, typename... A_>
struct HasMethodArithmeticReturn
{
    template <class U_>
    static Wrap<decltype(std::declval<U_>().NAME(std::declval<A_>()...))> test(void*);
    template <class U_>
    static Unknown test(...);

    typedef char yes[1];
    typedef char no[2];

    template <typename>
    struct helper
    {
        typedef no type;
    };
    template <typename R_>
    struct helper<Wrap<R_>>
    {
        template <class U_, R_(U_::*)(A_...)>
        struct assist
        {
            typedef yes type;
        };
        template <class U_>
        static typename std::enable_if<std::is_arithmetic<R_>::value, typename assist<U_, &U_::NAME>::type>::type& test(void*);
        template <class U_>
        static no& test(...);
        typedef decltype(test<C_>(0)) type;
    };
    static const bool value = sizeof(yes) == sizeof(typename helper<decltype(test<C_>(0))>::type);
};

You can see it used live. If the class has a method with A_... parameters and an arithmetic return type, value is true. Otherwise, it is false.

Weak to Enuma Elish
  • 4,622
  • 3
  • 24
  • 36
1

In addition to @James Root's answer, which allows specification of the arguments but doesn't allow the user to test multiple classes at the same time as in the OP, you may also do the following:

#include <type_traits>

template <typename... Other>
struct has_method {
    static constexpr bool value = has_method<Other...>::value;
};

template <typename U, typename... Other>
struct has_method<U, Other...> {
    static constexpr bool value =
        has_method<U>::value && has_method<Other...>::value;
};

template <typename U>
struct has_method<U> {
    template <typename T>
    static auto typeget(int) -> decltype(T::some_method(size_t{}));
    template <typename T>
    static void typeget(...);


    static constexpr bool value = 
        std::is_arithmetic<decltype(has_method::typeget<U>(0))>::value;
};

The implementation syntax is simpler, but you pay for it in other ways: you have to make a new template for every set of argument types you wish to supply to the function, and this detects whether the function may be called with the parameters, not whether the function has the exact desired parameters.

Live on Coliru, though I've replaced size_t with uint8_t to demonstrate that if the desired type can be implicitly converted to the argument type in the member function of the queried type, then the expression evaluates as true.

jaggedSpire
  • 4,423
  • 2
  • 26
  • 52
1

EDIT: I've updated the answer to allow for the given method to take arbitrary arguments, not simply have a void signature

I think this will do the trick:

namespace details {
  template<typename...>
  struct voider
  {
      using type=void;
  };

  template<typename T>
  struct GetReturnType;

  // non member function
  template<typename Ret, typename... Args>
  struct GetReturnType<Ret(*)(Args...)>
  {
      using type = Ret;
  };

  // mmeber function
  template<typename Ret, typename C, typename... Args>
  struct GetReturnType<Ret(C::*)(Args...)>
  {
      using type = Ret;
  };
}

template<typename...Ts>
using void_t = typename details::voider<Ts...>::type;

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

template<typename T>
struct has_arithmetic_method<T, void_t<decltype(&T::some_method)>> :  std::is_arithmetic<typename details::GetReturnType<decltype(&T::some_method)>::type>::type{};

I'm using the voider concept to make sure that accessing some_method on T is well-formed, and then deriving from std::is_arithmetic if it is. I had to add a helper to pull off the return type of an arbitrary function.


Some structs to test against. Notice how the ones that should pass take in different arguments for some_method:

struct FailSomeMethod // lacks a some_method altogether
{
    void some_other_method()
    {
        std::cout << "FailSomeMethod::some_other_method" << std::endl;
    }
};

struct PassSomeMethod // has a some_method, but it's not arithmetic
{
    void some_method()
    {
        std::cout << "PassSomeMethod::some_method" << std::endl;
    }
};

struct PassArithmeticMethod 
{
    int some_method(int _a)
    {
        std::cout << "PassArithmeticMethod::some_method" << std::endl;
        return 1;
    }
};

struct PassArithmeticMethod2
{
    double some_method()
    {
        return 1.0;
    }
};

struct PassArithmeticMethod3
{
    float some_method(int a, double b, float c, char d, bool e)
    {
        return 1.;
    }
};

The actual test:

int main()
{
    //test the has_arithmetic_method concept
    std::cout << "Struct PassArithmeticMethod: " << std::boolalpha << has_arithmetic_method<PassArithmeticMethod>::value << std::endl;
    std::cout << "Struct PassSomeMethod: " << std::boolalpha << has_arithmetic_method<PassSomeMethod>::value << std::endl;
    std::cout << "Struct FailSomeMethod: " << std::boolalpha << has_arithmetic_method<FailSomeMethod>::value << std::endl;

    std::cout << "Struct PassArithmeticMethod2: " << std::boolalpha << has_arithmetic_method<PassArithmeticMethod2>::value << std::endl;
    std::cout << "Struct PassArithmeticMethod3: " << std::boolalpha << has_arithmetic_method<PassArithmeticMethod3>::value << std::endl;
}

Outputs:

Struct PassArithmeticMethod: true
Struct PassSomeMethod: false
Struct FailSomeMethod: false
Struct PassArithmeticMethod2: true
Struct PassArithmeticMethod3: true

Live Demo

Honestly, despite the (misleading) length of my answer, I feel that this is the simplest. The voider concept and the GetReturnType helper are general purpose structs that can be easily re-used. I don't use any constexpr in my code; I leave all the work for getting true and false to inheritance tricks.

AndyG
  • 39,700
  • 8
  • 109
  • 143