Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Type trait: Check if class have specific function (maybe inherit)

I know that there are many possible ways to detect if a class has a specific function but non of them really work for my exact case. My current implementation to check for the correct member function works, except for inherit functions.

#include <type_traits>

template<typename T>                                                                
class HasFoo {                                                                                 
    template <typename U, int (U::*)(float)>                                  
      struct Check;                                                                 

    template <typename U>                                                       
      static std::true_type Test(Check<U, &U::foo> *);                 

    template <typename U>                                                           
      static std::false_type Test(...);                                               

public:
    static constexpr bool value = decltype(Test<T>(0))::value;                    
};

struct A {
  int foo(float);
};

struct B : public A {
};

struct C {
  unsigned int foo(double);
};

struct D {
  static int foo(float);
};

static_assert(HasFoo<A>::value, "A should have foo.");
static_assert(HasFoo<B>::value, "B should inherit foo from A.");

static_assert(!HasFoo<C>::value, "C should not have foo.");
static_assert(!HasFoo<D>::value, "Ds static foo should be false.");

Live example.

This implementation does not work for the static_assert of B.

An inacceptable workaround would be to check for:

template <typename U, int (U::A::*)(float)>
struct Check;                 |
                              |- add base class

But there I would have to know the base class and this should be avoided.

Does anyone have an idea how to check also for derivated functions?

Edit: The type trait should also work if no Foo at all exist.

struct E {};
static_assert(!HasFoo<E>::value, "E does not have foo.");
like image 294
Viatorus Avatar asked Apr 18 '16 11:04

Viatorus


3 Answers

Here is a way to do it (work for your 4 test cases, did not test it intensively though), thanks @Jarod42 for the improvement (see initial answer at the end):

template <typename T>
int call_foo (int (T::*)(float));

template <typename C>
std::true_type has_foo(decltype(call_foo(&C::foo)));

template <typename C>
std::false_type has_foo (...);    

template<typename T>
using HasFoo = decltype(has_foo<T>(0));

The problem with your code was that you were expecting U::* whereas &B::foo is A::* (not B::*). Here I let the compiler choose the value of T by using implicit type deduction so I don't run into such issue.

The code works as follow:

  • If T does not have a foo member, then the compiler will choose the second overload of has_foo.
  • If T does have a foo member, the compiler will try the first overload but will fail since there is no matching call_foo function so it will again choose the second one and make a std::false_type.

Working code on ideone: http://ideone.com/erh93I.

You can put everything in a class if you want:

template <typename T>
class HasFoo {

    template <typename C>
    static int call_foo (int (C::*)(float));

    template <typename C>
    static std::true_type has_foo (decltype(call_foo(&C::foo)));

    template <typename C>
    static std::false_type has_foo (...);

public:
    static constexpr bool value = decltype(has_foo<T>(0)){};
};
like image 175
Holt Avatar answered Oct 31 '22 02:10

Holt


Here is one old school C++03 way of doing it. Typically it can be used as a utility and get it molded for any method or variable.

#define HasMember(NAME) \
  template<class Class, typename Type = void> \
  struct HasMember_##NAME \
  { \
    typedef char (&yes)[2]; \
    template<unsigned long> struct exists; \
    template<typename V> static yes Check (exists<sizeof(static_cast<Type>(&V::NAME))>*); \
    template<typename> static char Check (...); \
    static const bool value = (sizeof(Check<Class>(0)) == sizeof(yes)); \
  }; \
  template<class Class> \
  struct HasMember_##NAME<Class, void> \
  { \
    typedef char (&yes)[2]; \
    template<unsigned long> struct exists; \
    template<typename V> static yes Check (exists<sizeof(&V::NAME)>*); \
    template<typename> static char Check (...); \
    static const bool value = (sizeof(Check<Class>(0)) == sizeof(yes)); \
  }

Instantiate:

HasMember(Foo);

Usage:

HasMember_Foo<B>::value  // without type (but then no overload allowed)
HasMember_Foo<C, int (C::*)(float)>::value  // needs type

Note that, here I am providing two HasMember_Foos, 1 with type and 1 without type. They are generalized for any type (not just specific to int (X::*)(float)). If there is no type mentioned, then the class must have only 1 such method (without overload). Hence, it's always safer to mention the type; As you have done in your question, the specific type is int (X::*)(float). BTW, this also can be included using another macro.
Without such extra macro, in case of class C and class D, you may have to specify the type of the method.

Here is a demo with your code.


Here it's assumed that whichever class member (function or variable) is chosen, must be public scoped. i.e. If X::foo is private then this solution will not work.

like image 25
iammilind Avatar answered Oct 31 '22 02:10

iammilind


Here is a solution with <experimental/type_traits> or <boost/type_traits.hpp>

#include <experimental/type_traits>
#include <iostream>

struct A {
  auto foo() { return 0; }
};
struct B {
  auto bar() { return 0.0; }
};
struct C : public A {
  auto bAr() { return 0.0; }
};
struct D : public C {
  auto baR() { return 0.0; }
};

template <typename T>
using HasFoo_t = decltype(std::declval<T&>().foo());

int main() {
  std::cout << std::experimental::is_detected_v<HasFoo_t, A> << std::endl;
  std::cout << std::experimental::is_detected_v<HasFoo_t, B> << std::endl;
  std::cout << std::experimental::is_detected_v<HasFoo_t, C> << std::endl;
  std::cout << std::experimental::is_detected_v<HasFoo_t, D> << std::endl;
}
like image 40
I.Omar Avatar answered Oct 31 '22 01:10

I.Omar