C++ compile-time predicate to test if a callable object of type F can be called with an argument of type T

前端 未结 3 506
庸人自扰
庸人自扰 2020-12-31 04:18

I would like to create a compile-type function that, given any callable object f (function, lambda expression, function object, ...) and a type T,

相关标签:
3条回答
  • 2020-12-31 04:56

    A variant of Paul's answer, but following the standard SFINAE test pattern. Again a generic trait with arbitrary parameter types A...:

    struct can_call_test
    {
        template<typename F, typename... A>
        static decltype(std::declval<F>()(std::declval<A>()...), std::true_type())
        f(int);
    
        template<typename F, typename... A>
        static std::false_type
        f(...);
    };
    
    template<typename F, typename... A>
    using can_call = decltype(can_call_test::f<F, A...>(0));
    

    Then a constexpr function as you requested:

    template<typename T, typename F>
    constexpr bool is_callable_with(F&&) { return can_call<F, T>{}; }
    

    Check live example.

    This will work with functions, lambda expressions, or function objects with arbitrary number of arguments, but for (pointers to) member functions you'll have to use std::result_of<F(A...)>.


    UPDATE

    Below, can_call has the nice "function signature" syntax of std::result_of:

    template<typename F, typename... A>
    struct can_call : decltype(can_call_test::f<F, A...>(0)) { };
    
    template<typename F, typename... A>
    struct can_call <F(A...)> : can_call <F, A...> { };
    

    to be used like this

    template<typename... A, typename F>
    constexpr can_call<F, A...>
    is_callable_with(F&&) { return can_call<F(A...)>{}; }
    

    where I've also made is_callable_with variadic (I can't see why it should be limited to one argument) and returning the same type as can_call instead of bool (thanks Yakk).

    Again, live example here.

    0 讨论(0)
  • 2020-12-31 05:12

    I would make a type trait first:

    template<class X = void>
    struct holder
    {
        typedef void type;
    };
    
    template<class F, class T, class X = void>
    struct is_callable_with_trait
    : std::false_type
    {};
    
    template<class F, class T>
    struct is_callable_with_trait<F, T, typename holder<
        decltype(std::declval<F>()(std::declval<T>()))
    >::type>
    : std::true_type
    {};
    

    And then if you want, you can turn it into a function:

    template<typename T, typename F>
    constexpr bool is_callable_with(F&&) 
    {
        return is_callable_with_trait<F&&, T>::value;
    }
    
    0 讨论(0)
  • 2020-12-31 05:18
    template<class F, class T, class = void>
    struct is_callable_with_impl : std::false_type {};
    
    template<class F, class T>
    struct is_callable_with_impl<F,T,
         typename std::conditional< 
                  true,
                  void,
                  decltype( std::declval<F>() (std::declval<T>()) ) >::type
          > : std::true_type {};
    
    template<class T, class F>
    constexpr bool is_callable_with(F &&) 
    { 
         return is_callable_with_impl< F, T >::value; 
    }
    

    It is basically the same solution as the one posted by Paul, I just prefer to use conditional<true, void, decltype( ... ) > instead of an holder class to avoid namespace pollution.

    0 讨论(0)
提交回复
热议问题