SFINAE сравнение подходов - PullRequest
6 голосов
/ 05 декабря 2010

Следующий код демонстрирует реализацию SFINAE, чтобы проверить, содержит ли тип (в основном класс) функцию-член member_func во время компиляции.

#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
}

Однако мой вопрос: какой подход вы бы предпочли (ПОДХОД 1 или ПОДХОД 2) и почему?
Находите ли вы несоответствие в данных подходах? Если да, пожалуйста, дайте мне знать.

P.S: Предположим, sizeof(char)!= sizeof(long)

Ответы [ 3 ]

1 голос
/ 05 декабря 2010

Второй подход не проверяет тип функции (возвращаемый тип или типы аргументов) и работает со всеми типами, не только с типами классов.

0 голосов
/ 05 декабря 2010

Я бы лично предпочел использовать второй подход, так как он короче и проще для понимания. Но GCC не скомпилирует его, поэтому вы должны использовать что-то подобное для 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)};
};

Также было бы неплохо избавиться от макроса CHECKER. Это делает ваш код чрезвычайно менее читабельным.

В любом случае, я бы воздержался от использования таких хаков C ++ в производственном коде (за исключением того, что вы являетесь членом команды Boost: -)

Такие вещи подвержены ошибкам, их трудно поддерживать, трудно переносимы между компиляторами, но главное, что я не помню ни одной реальной задачи, требующей такого жесткого кода C ++.

0 голосов
/ 05 декабря 2010

РЕДАКТИРОВАТЬ : завершено и исправлено.

Другой подход, использующий неоднозначность от наследования, вероятно, функционально эквивалентен вашему подходу 2. Я помню, что у меня были проблемы с подходом 1 (хотя он компилируется с G ++ 4.4.5), потому что разрешение имени вызвало ошибку, а не ошибку замены. Мне пришлось прибегнуть к:

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

Это работает, когда T является классом, поэтому вы можете добавить свой слой для проверки, является ли T типом класса.

Обратите внимание, что будет обнаружена любая функция-член foo. Если вы хотите проверить, может ли обнаруженная функция foo вызываться с заданными аргументами, вы должны сделать еще один слой 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;
};
...