开发者

SFINAE approach comparison

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
                                  开发者_StackOverflow         >*);
    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)


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


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++.


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;
};
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜