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

4

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.

Enisle answered 5/4, 2014 at 14:29 Comment(2)
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<T>(f, nullptr)).Ehudd
I'm the only one who is counting the days to start using Concepts on his C++ projects? :PFlavorous
B
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.

Bebe answered 5/4, 2014 at 14:59 Comment(3)
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?Carty
@Yakk Sure, that's better. I only wanted to keep the same signature as the code of the question.Bebe
Sweet, this works! Couple of nice tricks I didn't know about. Thanks!Enisle
A
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;
}
Angelaangele answered 5/4, 2014 at 14:50 Comment(2)
You have a typo, the 2nd false_type should be true_typeAbb
I typically call holder type_sink, as in it is a place types flow into and do not come out of.Carty
A
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.

Abb answered 5/4, 2014 at 14:58 Comment(1)
Then why not use decltype( std::declval<F>() (std::declval<T>()), void() )?Ehudd
B
0

In C++ 17 is now std::is_invocable:

static_assert( std::is_invocable_v<decltype(f1), int> );
static_assert( !std::is_invocable_v<decltype(f2), int> );

Took me a while to find, could save some time for someone.

Bodhisattva answered 11/10, 2023 at 13:18 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.