Search code examples
c++lambdafunctorgeneric-lambda

Check a type is a functor including generic lambda


Can I write a trait metafunction to figure out if a type is a functor or not? There are tons of code which can check functor using SFINAE of decltype(&T::operator()), for instance,

template<class T>
struct is_functor {
    template<class F>
    static auto test(decltype(&F::operator())) -> std::true_type;
    template<class F>
    static auto test(...) -> std::false_type;
    static constexpr bool value = decltype(test<T>(0))::value;
};

However, this doesn't work for generic lambda because generic lambda 's operator() is a template function.

There are some code for limited case for generic lambda version which make some constraints on the argument type of generic lambda. For instance, an answer here(https://stackoverflow.com/a/5117641/2580815) won't work if lambda expression contains any expression which cannot be valid for int type such as member access operation.

I don't need any generality for arity. In fact, I only need to know a type can be a functor which accepts only one parameter.

How can I implement my is_functor?

Use case:

I'm trying to validate if given parameter is a functor or not for a template function, that is, I want to like some overloaded tempalte functions, for instance:

template<class F, class = enable_if_t<is_functor<std::decay_t<F>>::value>>
auto make_func(F &&f) { return std::forward<F>(f); }
template<class F, class = enable_if_t<!is_functor<std::decay_t<F>>::value>>
auto make_func(F &&f) { return [f=std::forward<F>(f)] (auto&&) { return f; }; }

Solution

  • There is no proper way of doing this (at least until we get static reflection). The best you can do is check that an object is callable with a certain degree of confidence:

    1. Try getting its operator() address. If it fails, then the object may either be non-callable or its operator() could be overloaded/templated.

    2. Try calling the object with a dummy any_type instance that provides an interface for commonly used function. This might help you deduce its arity.

    3. If everything fails, force the user to somehow help the arity deduction or manually specify the arity.

    One way you could approach this is by having a deduced_arity set of tags:

    namespace deduced_arity
    {
        template <std::size_t TS>
        struct deducible_t : std::integral_constant<std::size_t, TS>
        {
        };
    
        struct undeducible_t
        {
        };
    
        constexpr undeducible_t undeducible{};
        constexpr deducible_t<1> unary{};
    }
    

    You will also need some sort of function_traits implementation that will statically tell you the exact arity of a function object. This can be found in Boost.

    Then you also need an implementation of any_type.

    Afterwards, you can use something like the following type trait to check whether or not a function object may be overloaded, using the detection idiom:

    template <typename T>
    using is_not_overloaded_impl = decltype(&T::operator());
    
    template <typename T>
    using is_not_overloaded =
        std::experimental::is_detected<is_not_overloaded_impl, T>;
    

    Then you can use a chain of if constexpr(...) (or any other compile-time branching mechanism) to make a good guess - example:

    if constexpr(is_not_overloaded<T>{})
    {
        // use `function_traits` here
    }
    else if constexpr(std::is_callable<T(any_type)>{})
    {
        return deduced_arity::unary;
    }
    else if constexpr(/* user manually marked arity */)
    {
        /* deal with user-defined deduction helpers */
    }
    else
    {
        return deduced_arity::undeducible;
    }