Возможен ли класс черты is_functor C ++? - PullRequest
12 голосов
/ 31 января 2012

Как я могу сделать статический вывод, если аргумент является функциональным объектом C ++ (функтор)?

template <typename F>
void test(F f) {}

Я пытался is_function<F>::value, но это не работает.Также кажется, что черты is_functor нет, поэтому, возможно, это невозможно.Я, кажется, ищу только определенную функцию-член, в этом случае оператор вызова функции: F::operator().

Ответы [ 3 ]

12 голосов
/ 04 сентября 2013

Можно создать такую ​​черту с двумя ограничениями:

  1. Для компилятора свободная функция принципиально отличается от функтора класса, который перегружает operator(). Таким образом, мы должны рассматривать оба случая отдельно при реализации. Это не проблема для использования, хотя мы можем скрыть детали реализации от пользователя.
  2. Нам нужно знать сигнатуру функции, которую вы хотите вызвать. Обычно это не проблема, и у нее есть приятный побочный эффект - наша черта способна довольно естественным образом справляться с перегрузками.

Шаг первый: бесплатные функции

Давайте начнем со бесплатных функций, потому что их немного легче обнаружить. Наша задача, когда передается указатель функции, определить, совпадает ли подпись этого указателя функции с подписью, переданной в качестве второго аргумента шаблона. Чтобы иметь возможность сравнивать их, нам нужно либо понять основную сигнатуру функции, либо создать указатель на функцию нашей сигнатуры. Я произвольно выбрал последнее:

// build R (*)(Args...) from R (Args...)
// compile error if signature is not a valid function signature
template <typename, typename>
struct build_free_function;

template <typename F, typename R, typename ... Args>
struct build_free_function<F, R (Args...)>
{ using type = R (*)(Args...); };

Теперь все, что осталось сделать, это сравнить, и мы закончили с бесплатной функциональной частью:

// determine whether a free function pointer F has signature S
template <typename F, typename S>
struct is_function_with_signature
{
    // check whether F and the function pointer of S are of the same
    // type
    static bool constexpr value = std::is_same<
        F, typename build_free_function<F, S>::type
    >::value;
};

Шаг второй: функторы класса

Этот немного более сложный. С помощью SFINAE мы можем легко определить, определяет ли класс operator():

template <typename T>
struct defines_functor_operator
{
    typedef char (& yes)[1];
    typedef char (& no)[2];

    // we need a template here to enable SFINAE
    template <typename U> 
    static yes deduce(char (*)[sizeof(&U::operator())]);
    // fallback
    template <typename> static no deduce(...);

    static bool constexpr value = sizeof(deduce<T>(0)) == sizeof(yes);
};

но это не говорит нам, существует ли кто-то для нашей желаемой сигнатуры функции! К счастью, здесь можно использовать хитрость: указатели являются допустимыми параметрами шаблона. Таким образом, мы можем сначала использовать указатель на функцию-член нашей желаемой подписи и проверить, относится ли &T::operator() к этому типу:

template <typename T, T> struct check;

Теперь check<void (C::*)() const, &C::operator()> будет допустимым экземпляром шаблона, только если C действительно имеет void C::operator()() const. Но для этого нам сначала нужно объединить C и подпись для указателя на функцию-член. Как мы уже видели, нам нужно беспокоиться о двух дополнительных случаях, о которых нам не нужно было заботиться о свободных функциях: const и volatile функции. Кроме того, это почти то же самое:

// build R (C::*)(Args...) from R (Args...)
//       R (C::*)(Args...) const from R (Args...) const
//       R (C::*)(Args...) volatile from R (Args...) volatile
// compile error if signature is not a valid member function signature
template <typename, typename>
struct build_class_function;

template <typename C, typename R, typename ... Args>
struct build_class_function<C, R (Args...)>
{ using type = R (C::*)(Args...); };

template <typename C, typename R, typename ... Args>
struct build_class_function<C, R (Args...) const>
{ using type = R (C::*)(Args...) const; };

template <typename C, typename R, typename ... Args>
struct build_class_function<C, R (Args...) volatile>
{ using type = R (C::*)(Args...) volatile; };

Объединяя это и наши выводы относительно вспомогательной структуры check, мы получаем нашу мета-функцию проверки для объектов функторов:

// determine whether a class C has an operator() with signature S
template <typename C, typename S>
struct is_functor_with_signature
{
    typedef char (& yes)[1];
    typedef char (& no)[2];

    // helper struct to determine that C::operator() does indeed have
    // the desired signature; &C::operator() is only of type 
    // R (C::*)(Args...) if this is true
    template <typename T, T> struct check;

    // T is needed to enable SFINAE
    template <typename T> static yes deduce(check<
        typename build_class_function<C, S>::type, &T::operator()> *);
    // fallback if check helper could not be built
    template <typename> static no deduce(...);

    static bool constexpr value = sizeof(deduce<C>(0)) == sizeof(yes);
};

Шаг третий: Соединение частей

Мы почти закончили. Теперь нам нужно только решить, когда использовать нашу свободную функцию, а когда метафункции функтора класса. К счастью, C ++ 11 предоставляет нам черту std::is_class, которую мы можем использовать для этого. Поэтому все, что нам нужно сделать, это специализироваться на булевом параметре:

// C is a class, delegate to is_functor_with_signature
template <typename C, typename S, bool>
struct is_callable_impl
    : std::integral_constant<
        bool, is_functor_with_signature<C, S>::value
      >
{};

// F is not a class, delegate to is_function_with_signature
template <typename F, typename S>
struct is_callable_impl<F, S, false>
    : std::integral_constant<
        bool, is_function_with_signature<F, S>::value
      >
{};

Итак, мы можем наконец добавить последний кусочек головоломки, являясь нашей реальной чертой is_callable:

// Determine whether type Callable is callable with signature Signature.
// Compliant with functors, i.e. classes that declare operator(); and free
// function pointers: R (*)(Args...), but not R (Args...)!
template <typename Callable, typename Signature>
struct is_callable
    : is_callable_impl<
        Callable, Signature,
        std::is_class<Callable>::value
      >
{};

Теперь мы очистим наш код, поместим детали реализации в анонимные пространства имен, чтобы они не были доступны за пределами нашего файла, и получили хороший is_callable.hpp для использования в нашем проекте.

Полный код

namespace // implementation detail
{
    // build R (*)(Args...) from R (Args...)
    // compile error if signature is not a valid function signature
    template <typename, typename>
    struct build_free_function;

    template <typename F, typename R, typename ... Args>
    struct build_free_function<F, R (Args...)>
    { using type = R (*)(Args...); };

    // build R (C::*)(Args...) from R (Args...)
    //       R (C::*)(Args...) const from R (Args...) const
    //       R (C::*)(Args...) volatile from R (Args...) volatile
    // compile error if signature is not a valid member function signature
    template <typename, typename>
    struct build_class_function;

    template <typename C, typename R, typename ... Args>
    struct build_class_function<C, R (Args...)>
    { using type = R (C::*)(Args...); };

    template <typename C, typename R, typename ... Args>
    struct build_class_function<C, R (Args...) const>
    { using type = R (C::*)(Args...) const; };

    template <typename C, typename R, typename ... Args>
    struct build_class_function<C, R (Args...) volatile>
    { using type = R (C::*)(Args...) volatile; };

    // determine whether a class C has an operator() with signature S
    template <typename C, typename S>
    struct is_functor_with_signature
    {
        typedef char (& yes)[1];
        typedef char (& no)[2];

        // helper struct to determine that C::operator() does indeed have
        // the desired signature; &C::operator() is only of type 
        // R (C::*)(Args...) if this is true
        template <typename T, T> struct check;

        // T is needed to enable SFINAE
        template <typename T> static yes deduce(check<
            typename build_class_function<C, S>::type, &T::operator()> *);
        // fallback if check helper could not be built
        template <typename> static no deduce(...);

        static bool constexpr value = sizeof(deduce<C>(0)) == sizeof(yes);
    };

    // determine whether a free function pointer F has signature S
    template <typename F, typename S>
    struct is_function_with_signature
    {
        // check whether F and the function pointer of S are of the same
        // type
        static bool constexpr value = std::is_same<
            F, typename build_free_function<F, S>::type
        >::value;
    };

    // C is a class, delegate to is_functor_with_signature
    template <typename C, typename S, bool>
    struct is_callable_impl
        : std::integral_constant<
            bool, is_functor_with_signature<C, S>::value
          >
    {};

    // F is not a class, delegate to is_function_with_signature
    template <typename F, typename S>
    struct is_callable_impl<F, S, false>
        : std::integral_constant<
            bool, is_function_with_signature<F, S>::value
          >
    {};
}

// Determine whether type Callable is callable with signature Signature.
// Compliant with functors, i.e. classes that declare operator(); and free
// function pointers: R (*)(Args...), but not R (Args...)!
template <typename Callable, typename Signature>
struct is_callable
    : is_callable_impl<
        Callable, Signature,
        std::is_class<Callable>::value
      >
{};

Идеальный пример с некоторыми тестами

http://ideone.com/7PWdiv

0 голосов
/ 23 июля 2016

Хотя это не работает для перегруженных функций, во всех других случаях (свободные функции, классы, реализующие operator() и лямбда-выражения) это краткое решение работает в C ++ 11:

template <typename T, typename Signature>
struct is_callable: std::is_convertible<T,std::function<Signature>> { };

Примечание: std::is_callable будет доступно на C ++ 17.

0 голосов
/ 01 февраля 2012
template<typename T, typename Sign>                                 
struct is_functor 
{                                                                   
    typedef char yes[1];                                            
    typedef char no [2];                                            
    template <typename U, U> struct type_check;                     
    template <typename _1> static yes &chk(type_check<Sign, &_1::operator()>*);
    template <typename   > static no  &chk(...);                    
    static bool const value = sizeof(chk<T>(nullptr)) == sizeof(yes);     
};

Изменено с этот ответ .

Можно использовать как ...

template<typename T>
typename std::enable_if<is_functor<T, void(T::*)()>::value>::type func()
{
}
...