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

冷暖自知 提交于 2019-11-29 11:34:54

问题


I would like to create a compile-type function that, given any callable object f (function, lambda expression, function object, ...) and a type T, evaluates to true, if f can be called with an argument of type T, and false if it cannot.

Example:

void f1(int) { ... }
void f2(const std::string&) { ... }

assert( is_callable_with<int>(f1));
assert(!is_callable_with<int>(f2));

I'm thinking that a clever use of the SFINAE rule could achieve this. Possibly somehow like this:

template<typename T, typename F>
constexpr bool is_callable_with(F&&, typename std::result_of<F(T)>::type* = nullptr) {
  return true;
}

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

But this doesn't work, because if F is callable with T, both overloads participate in the overload resolution and there is an ambiguity. I'd like to rewrite it so in the positive case, the first overload would be picked by the overload resolution over the second one. Not sure if I'm even on the right track here though.


回答1:


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.




回答2:


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



回答3:


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.



来源:https://stackoverflow.com/questions/22882170/c-compile-time-predicate-to-test-if-a-callable-object-of-type-f-can-be-called

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