Search code examples
c++c++14type-traits

Type traits to check if class has member function


Trying to create a way to identify if a given class has a given function that can be invoked, and returns some type.

Any idea on what I'm doing wrong here? Is there a better way to determine if a given method is invoke'able given a class?

#include <string>
#include <type_traits>

#define GENERATE_HAS_MEMBER_FUNC(func, rettype)                                \
template<typename T, class Enable = void> struct has_##func;                   \
template<typename T, class U> struct has_##func : std::false_type {};          \
template<typename T>                                                           \
struct has_##func<T,                                                           \
                  typename std::enable_if<std::is_same<                        \
                      typename std::result_of<decltype (&T::func)(T)>::type,   \
                      rettype>::value>::type> : std::true_type{};              \
template<class T> constexpr bool has_##func##_v = has_##func<T>::value;

GENERATE_HAS_MEMBER_FUNC(str, std::string)
GENERATE_HAS_MEMBER_FUNC(str2, std::string)
GENERATE_HAS_MEMBER_FUNC(funca, std::string)
GENERATE_HAS_MEMBER_FUNC(strK, std::string)
GENERATE_HAS_MEMBER_FUNC(fancy, std::string)
GENERATE_HAS_MEMBER_FUNC(really, std::string)

struct A1 {
    virtual std::string str() const { return ""; }
    std::string strK() const { return ""; }
    virtual std::string fancy()=0;
};

struct A2 : A1 {
    std::string str() const override { return ""; }
    std::string funca();
    std::string fancy() override { return ""; }
    std::string really(int a=0) const { return std::to_string(a); }

};

int main() {
    static_assert(has_str_v<A1>,
        "A1::str is virtual method with impl on base"); // MSVC: NO, clang: OK, GCC: NO
    static_assert(has_strK_v<A1>,
        "A1::strK is implemented inline "); // MSVC: NO, clang: OK, GCC: NO
    static_assert(has_fancy_v<A1>,
        "A1::fancy is a pure virtual method on base"); // MSVC: NO, clang: OK, GCC: NO
    static_assert(!has_really_v<A1>,
        "A1::really doesn't exist in A1"); // MSVC: OK, clang: OK, GCC: OK

    static_assert(has_str_v<A2>,
        "A2::str is override method "); // MSVC: OK, clang: OK, GCC: OK
    static_assert(!has_str2_v<A2>,
        "A2::str2 does not exist in A2"); // MSVC: NO, clang: OK, GCC: OK
    static_assert(has_funca_v<A2>,
        "A2::funca is defined (no impl) in A2"); // MSVC: OK, clang: OK, GCC: OK
    static_assert(has_strK_v<A2>,
        "A2::strK is implemented method on base"); // MSVC: OK, clang: OK, GCC: OK
    static_assert(has_fancy_v<A2>,
        "A1::fancy is a override of pure virtual method of base"); // MSVC: OK, clang: OK, GCC: OK
    static_assert(has_really_v<A2>,
        "A2::really has default param (can be invoked without params)"); // MSVC: OK, clang: NO, GCC: NO
    return 0;
}

Some surprises on this implementation.

EDIT: Upon trying to implement @Jarod42 and @Vittorio Romeo awesome suggestions:

#define GENERATE_HAS_MEMBER_FUNC(func, rettype)                                \
template<class T> using _has_##func_chk =                                      \
      decltype(std::declval<T &>().func());                                    \
template<class T> constexpr bool has_##func##_v =                             \
      is_detected_exact_v<rettype, _has_##func_chk, T>;

now two test cases still fail on VS2015 (doesn't make any sense): static_assert(!has_really_v, "A1::really doesn't exist in A1"); static_assert(!has_str2_v, "A2::str2 does not exist in A2");

there is probably something silly something that I'm missing... any clues?


Solution

  • Is there a better way to determine if a given method is invoke'able given a class?

    Yes, you can use the detection idiom, which can be implemented in C++11 (the linked page contains a valid implementation).

    Here's an example: does Cat have a float Cat::purr(int) method?

    struct Cat { float purr(int){} };
    
    template<class T>
    using has_purr = 
        decltype(std::declval<T&>().purr(std::declval<int>()));
        
    static_assert(std::experimental::is_detected_exact_v<float, has_purr, Cat>);
    

    wandbox example


    The required detection idiom C++17 dependencies are trivial to implement in C++11:

    template< class... >
    using void_t = void;
    
    struct nonesuch {
        nonesuch() = delete;
        ~nonesuch() = delete;
        nonesuch(nonesuch const&) = delete;
        void operator=(nonesuch const&) = delete;
    };
    

    Here's a fully C++11-compliant minimal example on wandbox.