Is an is_functor C++ trait class possible?

余生长醉 提交于 2019-12-12 07:09:58

问题


How can I deduce statically if an argument is a C++ function object (functor)?

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

I tried is_function<F>::value, but this doesn't work. It also seems there is no is_functor trait, so perhaps it's not possible. I appear to be only looking for a specific member function, in this case the function call operator: F::operator().


回答1:


It is possible to create such a trait, with two restrictions:

  1. For the compiler, a free function is something fundamentally different from a class functor that overloads operator(). Thus we have to treat both cases seperately when implementing. This is not a problem for usage though, we can hide this implementation detail from the user.
  2. We need to know the signature of the function you want to call. This is usually not a problem, and it does have the nice side effect that our trait is able to handle overloads pretty natively.

Step one: Free functions

Let's start with free functions, because they are little easier to detect. Our task is, when given a function pointer, to determine whether the signature of that function pointer matches the signature passed as the second template argument. To be able to compare those, we either need to get a grasp of the underlying function signature, or create a function pointer of our signature. I arbitrarily chose the latter:

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

Now all that's left to do is to compare and we are done with the free function part:

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

Step two: Class functors

This one is a little more involved. We could easily detect with SFINAE whether a class defines an 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);
};

but that does not tell us whether one exists for our desired function signature! Luckily, we can use a trick here: pointers are valid template parameters. Thus we can first use the member function pointer of our desired signature, and check whether &T::operator() is of that type:

template <typename T, T> struct check;

Now check<void (C::*)() const, &C::operator()> will only be a valid template instantiation if C does indeed have a void C::operator()() const. But to do this we first have to combine C and the signature to a member function pointer. As we already have seen, we need to worry about two extra cases we did not have to care about for free functions: const and volatile functions. Besides that it's pretty much the same:

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

Putting that and our findings concerning the check helper struct together, we get our check metafunction for functor objects:

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

Step three: Putting the pieces together

We are almost done. Now we only need to decide when to use our free function, and when the class functor metafunctions. Luckily, C++11 provides us with a std::is_class trait that we can use for this. So all we have to do is specialize on a boolean parameter:

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

So we can finally add the last piece of the puzzle, being our actual is_callable trait:

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

Now we clean up our code, put implementation details into anonymous namespaces so they are not acessible outside of our file, and have a nice is_callable.hpp to use in our project.

Full code

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

Ideone example with some tests

http://ideone.com/7PWdiv




回答2:


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

Altered from this answer.

It could be used like...

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



回答3:


Although this does not work for overloaded functions, for all other cases (free functions, classes implementing operator(), and lambdas) this short solutions works in C++11:

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

Note: std::is_callable will be available in C++17.



来源:https://stackoverflow.com/questions/9083593/is-an-is-functor-c-trait-class-possible

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!