Возможен ли трейт-класс is_functor C++?

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

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

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


person user2023370    schedule 31.01.2012    source источник
comment
как насчет is_function<F::operator()>::value?   -  person Fiktik    schedule 31.01.2012
comment
groups.google.com/group/comp.lang.c++. moded/msg/ может вас заинтересовать.   -  person pmr    schedule 31.01.2012
comment
Вы просто хотите протестировать функторы или любой вызываемый объект? Кажется, что некоторое использование трейта result_of в SFINAE будет работать для идентификации любого вызываемого типа. Я немного удивлен, что, похоже, еще нет черты std::is_callable.   -  person bames53    schedule 31.01.2012
comment
@bames53: Я смотрю на result_of таким образом весь день. Кажется, что есть много ситуаций, когда GCC выдает ошибку в одной перегрузке, хотя существует другая допустимая перегрузка.   -  person user2023370    schedule 01.02.2012
comment
@Fitkit: это не компилируется, и is_function<typename F::operator()>::value также отклоняется: invalid template argument.   -  person user2023370    schedule 01.02.2012
comment
Нет такой вещи, как F::operator(). Все функции-члены имеют список аргументов, даже если этот список пуст. Вместо этого вы ищете существование F::operator()()? Да, это возможно обнаружить.   -  person Howard Hinnant    schedule 01.02.2012


Ответы (3)


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

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

Шаг 1. Бесплатные функции

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

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

Шаг 3. Соединяем фрагменты

Мы почти закончили. Теперь нам нужно только решить, когда использовать нашу бесплатную функцию, а когда метафункции функтора класса. К счастью, 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

person nijansen    schedule 04.09.2013

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

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

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

person SU3    schedule 23.07.2016

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()
{
}
person David    schedule 01.02.2012
comment
typename decltype? И ваше решение не работает, если operator() перегружен. - person kennytm; 01.02.2012
comment
Ваше определение функтора неполное. Стандартный функтор — это либо указатель на функцию, либо объект с перегруженным operator(). - person Maxim Egorushkin; 01.02.2012
comment
Я разместил другое решение; @MaximYegorushkin, а новый в этом плане не меняется, хммм - person David; 01.02.2012