0

I've written a trait that detects whether a class has a certain public member function via std::is_member_function_pointer:

class Test1
{
public:
    void method();
};

class Test2
{
public:
    void method();
    void method(int);
};

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

template <typename T>
struct HasMethod<T, std::enable_if_t<std::is_member_function_pointer<decltype(&T::method)>::value>>
    : std::true_type
{
};

int main()
{
    std::cout << HasMethod<Test1>::value << std::endl; // prints 1
    std::cout << HasMethod<Test2>::value << std::endl; // prints 0
}

However it seems that when the function is overloaded, then std::is_member_function_pointer is not able to detect it, as shown with Test2. Is there a way to make it work regardless of whether the method is overloaded?

zhanginou
  • 177
  • 6
  • 4
    It is not a problem with `std::is_member_function_pointer`. It is a problem of `&T::method`: which overload's address should be returned in this case? – sklott Feb 17 '23 at 18:25
  • Good point. I guess I would need to disambiguate by casting the pointer to a given signature. But is there a way to templatize this trait so that any signature can work? – zhanginou Feb 17 '23 at 18:44
  • 2
    Why would you want any signature to work? The eventual goal would be to call that function, no? You can't call it if you don't know what arguments to pass. – Yksisarvinen Feb 17 '23 at 18:54
  • I got it to work, check edited answer below. – sklott Feb 18 '23 at 12:30

1 Answers1

1

To detect existence of class members usual approach is to use std::void_t template. You can do it like this:

#include <iostream>
#include <type_traits>

class Test1
{
public:
    void method();
};

class Test2
{
public:
    void method();
    void method(int);
};

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

// Use method(0) to detect void method(int) overload
// You don't even need std::void_t in this case, since method returns void anyway, but for any other return type you will need it
template <typename T>
struct HasMethod<T, std::void_t<decltype(std::declval<T>().method())>>
    : std::true_type
{
};

int main()
{
    std::cout << HasMethod<Test1>::value << std::endl; // prints 1
    std::cout << HasMethod<Test2>::value << std::endl; // prints 1
}

Ok. After some thinking I found solution. We can use fact that declval(&T::method) will fail, if there is more than one overload, to detect if we have at least one overload, by adding another one. Here is solution. It is quite verbose, but I was unable to reduce it. At least it works.

#include <iostream>
#include <type_traits>

class Test
{
};

class Test1
{
public:
    void method();
};

class Test2
{
public:
    void method();
    void method(int);
};

class Test3
{
public:
    using method = int;
};

class Test4
{
public:
    int method;
};

template<typename T, typename = void>
struct HasSingleOverload : std::false_type {};
template<typename T>
struct HasSingleOverload<T, std::void_t<decltype(&T::method)>> : std::true_type {};

template<typename T, typename = void>
struct IsMemberFunction : std::false_type {};
template<typename T>
struct IsMemberFunction<T, std::enable_if_t<std::is_member_function_pointer<decltype(&T::method)>::value>> : std::true_type {};

template<typename T, typename = void>
struct IsType : std::false_type {};
template<typename T>
struct IsType<T, std::void_t<typename T::method>> : std::true_type {};

struct HasOverload {
    void method();
};

template<typename T>
struct CheckOverload : T, HasOverload {
};

template<typename T>
using HasConflict = std::bool_constant<!HasSingleOverload<CheckOverload<T>>::value>;

template<typename T>
using HasAnyOverload = std::conditional_t<HasSingleOverload<T>::value || IsType<T>::value, IsMemberFunction<T>, HasConflict<T>>;

int main()
{
    std::cout << HasAnyOverload<Test>::value << std::endl; // prints 0
    std::cout << HasAnyOverload<Test1>::value << std::endl; // prints 1
    std::cout << HasAnyOverload<Test2>::value << std::endl; // prints 1
    std::cout << HasAnyOverload<Test3>::value << std::endl; // prints 0
    std::cout << HasAnyOverload<Test4>::value << std::endl; // prints 0
}
sklott
  • 2,634
  • 6
  • 17