SFINAE Approach Comparison

The following code shows an implementation of SFINAE 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 these approaches? If so, let me know.

PS: Suppose sizeof(char)!= sizeof(long)

+6
c ++ templates sfinae
source share
3 answers

The second approach does not check the type of the function (return type or argument types) and works with all types, not just class types.

+1
source share

I personally would prefer the second approach to the game, as it is shorter and easier to understand. But GCC will not compile it, so you should use something like this 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)}; }; 

It would also be nice to get rid of the CHECKER macro. This makes your code extremely less readable.

In any case, I would refrain from using such C ++ hacks in production code (except that you are a member of the Boost team :-)

Such things are error prone, difficult to maintain, hardly portable between compilers, but the main thing is that I don’t remember that any real task required such hard C ++ code.

0
source share

EDIT : completed and fixed.

Another approach that uses ambiguity in inheritance is probably functionally equivalent to your approach 2. I remember that I have problems with approach 1 (it compiles with g ++ 4.4.5), because name resolution caused an error, not a replacement 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 can add your own layer to check if T is a class type.

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

 // 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; }; 
-one
source share

All Articles