6

The following code shows an SFINAE implementation to check whether a type (basically a class) contains a member function member_func at compile time.

#define CHECKER(func_name,class_name) sizeof(class_name<T>::template func_name<T>(0)) == 1
#include <iostream>
struct A
{
    void member_func();
};
struct B {};
template<typename T>struct Check_If_T_Is_Class_Type
{
    template<typename C> static char func (char C::*p);
    template<typename C> static long func (...);
    enum{val = CHECKER(func,Check_If_T_Is_Class_Type)};
};

//APPROACH 1
template <typename T>struct TypeHasMemberFunc
{
    template <typename C, C> struct TypeCheck;
    template <typename C> struct Prototype_Holder {typedef void (C::*fptr)();};
    template <typename C> static char func(TypeCheck
                                           <
                                              typename Prototype_Holder<C>::fptr,
                                              &C::member_func
                                           >*);
    template <typename C> static long func(...);
    enum {value = CHECKER(func,TypeHasMemberFunc)};
};

//APPROACH 2
template <typename T>struct has_member_func
{
    template<class C> static char func(char (*)[sizeof(&C::member_func)]);
    template<class C> static long func(...);
    enum{value = CHECKER(func,has_member_func)};
};
int main(){
 if(Check_If_T_Is_Class_Type<A>::val)
   std::cout<<TypeHasMemberFunc<A>::value; //using APPROACH 1

 if(Check_If_T_Is_Class_Type<B>::val)
   std::cout<<has_member_func<B>::value; //using APPROACH 2
}

However my question is which approach would you prefer (APPROACH 1 or APPROACH 2) and why?
Do you find any inconsistency in the given approaches? If yes please let me know.

P.S : Assume sizeof(char)!= sizeof(long)

Prasoon Saurav
  • 91,295
  • 49
  • 239
  • 345
  • Consult also http://stackoverflow.com/questions/257288/is-it-possible-to-write-a-c-template-to-check-for-a-functions-existence/3627243#3627243. It has some very good answers. – FireAphis Jan 03 '11 at 17:19

3 Answers3

1

Second approach doesn't check function type (return type or arguments types) and does work with all types, not only class types.

Pawel Zubrycki
  • 2,703
  • 17
  • 26
0

I would personally prefer the second approach to play with as it is shorter and easier to understand. But GCC won't compile it, so you have to use something like that for GCC:

namespace detail
{
    template<class C> char func(char (*)[sizeof(&C::member_func)]);
    template<class C> long func(...);   
}

template <typename T>struct has_member_func
{
    enum{value = (sizeof(detail::func<T>(0)) == 1)};
};

Also, It would be nice to get rid of CHECKER macro. It makes your code extremely less readable.

Anyway, I would refrain from using such C++ hacks in production code (except you are a Boost team member :-)

Such stuff is error-prone, hard to support, hardly portable between compilers but the principal point is that I do not remember any real life task demanded such hard-code C++.

Stas
  • 11,571
  • 9
  • 40
  • 58
-1

EDIT: completed and corrected.

Another approach, using ambiguity from inheritance, probably functionally equivalent to your Approach 2. I remember having problems with Approach 1 (it compiles with G++ 4.4.5 though) because the name resolution triggered an error and not a substitution failure. I had to resort to:

template <class T>
struct has_foo
{
  struct fallback { void foo(...); };
  struct D : T, fallback { };

  template <typename U, U> struct K;

  // Will be ambiguous for U = D iff T has a foo member function.                                                                                                         
  // It doesn't trigger an error, since D will always have at least one                                                                                                   
  // foo member function.                                                                                                                                                 
  template <class U> static char (&test(K<void (fallback::*)(...), &U::foo>*))[1];
  template <class U> static char (&test(...))[2];

  static const bool value = sizeof(test<D>(0)) == 2;
};

This works when T is a class, so you may want to add your layer for checking whether T is a class type.

Note that any foo member function will be detected. If you want to check whether the detected foo function can be called with given arguments, you have to do another layer of SFINAE:

// Check whether foo can be called with an argument of type Arg
// and yields an element of type Res.
// If you need Res = void, this code does not work.
template <class T, typename Arg, typename Res>
struct check_foo
{
    struct flag {};
    struct D : T { using T::foo; flag foo(...); };

    template <typename U>
    static char (&test(U))[1];

    template <typename> static char (&test(...))[2];

    static Arg f();

    static const bool value = sizeof(test<Arg>( ((D*)0)->foo(f()) )) == 1;
};
Alexandre C.
  • 55,948
  • 11
  • 128
  • 197
  • `so you may want to add another layer for checking whether T is a class type ` Already taken care of. Check out my code sample. `Check_If_T_Is_Class_Type` checks whether a type `T` is class type or not. – Prasoon Saurav Dec 05 '10 at 11:49
  • 1
    @Alexandre C...you said *triggers a plain error and not a substitution failure, since member_func will not in general be a member of C*... so you mean, member_func must be a member of C so as to cause 'substitution failure'? how does it make sense? – Nawaz Dec 05 '10 at 14:38
  • I believe you need `K`? If you pass `U`, you can't pass `&U::foo` because it would need an implicit conversion from `void(fallback::*)(...)` to `void(D::*)(...)`. – Johannes Schaub - litb Dec 05 '10 at 15:39
  • Also, your code won't be able to detect a specific function type. The code is more like "check whether there is a member in that class with a given name". It won't check for only a member function in particular. – Johannes Schaub - litb Dec 05 '10 at 15:46
  • @Nawaz, @litb, edited. The code which was written in a hurry now works. Johannes you're right in saying that this method only tests for existence of a member function with a specific name. I needed once to check whether a class was a functor which was callable with a given signature (possibly with conversions) and I used this exact approach, with another sfinae mechanism to check for the signature. As for the plain error thing, I remember having problems with spelling `foo` like OP did, it triggered an error and not a substitution failure and I had to use the inheritance approach. – Alexandre C. Dec 05 '10 at 18:19
  • @Alexandre C... have you compiled and tested this implementation? I think, you cannot pass `&U::foo' as template argument, since it's not a `type`, it's `value` (address of member function). – Nawaz Dec 05 '10 at 19:21
  • @nawaz: it compiles fine with gcc 4.4.5. Pointers to member functions can be passed as template arguments. – Alexandre C. Dec 05 '10 at 19:22
  • I also wrote such a thing using another approach, specifically designed to test callable-ness with op(). To be found at http://ideone.com/eXsxP – Johannes Schaub - litb Dec 05 '10 at 19:25