17

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.

Constructor
  • 7,273
  • 2
  • 24
  • 66
adam
  • 791
  • 6
  • 21
  • Well you have to make one overload a worse match for the arguments. For example, you can add an ellipsis to the second overload and add a function that selects one of those two overloads by adding an additional argument `nullptr` (calling `is_callable_with(f, nullptr)`). – dyp Apr 05 '14 at 14:49
  • I'm the only one who is counting the days to start using Concepts on his C++ projects? :P – Manu343726 Apr 05 '14 at 19:58

3 Answers3

12

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.

Community
  • 1
  • 1
iavr
  • 7,547
  • 1
  • 18
  • 53
  • Why return `bool` when you can return `std::true_type` or `std::false_type`? As written given a non-`constexpr` function pointer, is the return value a compile time constant? – Yakk - Adam Nevraumont Apr 05 '14 at 15:59
  • @Yakk Sure, that's better. I only wanted to keep the same signature as the code of the question. – iavr Apr 05 '14 at 16:08
  • Sweet, this works! Couple of nice tricks I didn't know about. Thanks! – adam Apr 05 '14 at 20:57
7

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;
}
Paul Fultz II
  • 17,682
  • 13
  • 62
  • 59
5
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.

sbabbi
  • 11,070
  • 2
  • 29
  • 57