29

Is it possible to write a C++(0x) metafunction that determines whether a type is callable?

By callable type I mean a function type, function pointer type, function reference type (these are detected by boost::function_types::is_callable_builtin), lambda types, and any class with an overloaded operator() (and maybe any class with an implicit conversion operator to one of these, but that's not absolutely necessary).

EDIT: The metafunction should detect the presence of an operator() with any signature, including a templated operator(). I believe this is the difficult part.

EDIT: Here is a use case:

template <typename Predicate1, typename Predicate2>
struct and_predicate
{
    template <typename ArgT>
    bool operator()(const ArgT& arg)
    {
        return predicate1(arg) && predicate2(arg);
    }

    Predicate1 predicate1;
    Predicate2 predicate2;
};

template <typename Predicate1, typename Predicate2>
enable_if<ice_and<is_callable<Predicate1>::value,
                  is_callable<Predicate2>::value>::value,
          and_predicate<Predicate1, Predicate2>>::type
operator&&(Predicate1 predicate1, Predicate2 predicate2)
{
    return and_predicate<Predicate1, Predicate2>{predicate1, predicate2};
}

is_callable is what I would like to implement.

HighCommander4
  • 50,428
  • 24
  • 122
  • 194
  • Why do you want to check if an object is callable? What is it you're trying to do? – wilhelmtell Feb 24 '11 at 03:35
  • For example, write an overloaded operator&& that takes two predicates A and B and returns a predicate C such that C(x) iff. A(x) && B(x). Obviously, not restricting such an operator to callable objects causes havoc elsewhere. – HighCommander4 Feb 24 '11 at 03:47
  • I like how C++ templates require you to *not* match things that you don't like, rather than match things you do. In D, this would just translate into a single preexisting template `isCallable!(T)`, and its definition is nowhere as indirect as in C++... :) – user541686 Feb 24 '11 at 05:11
  • @Mehrdad I don't follow. I want to define this operator for callable objects only, so I want to match callable objects. – HighCommander4 Feb 24 '11 at 05:16
  • @HighCommander4: Yeah, but the trouble is, you're forced to make templates that *don't* match *other* objects. See [this link](http://www.digitalmars.com/d/2.0/templates-revisited.html) for details... it's called "SFINAE (Substitution Failure Is Not An Error". – user541686 Feb 24 '11 at 05:24
  • @Mehrdad OK, I see what you mean now, but I don't see how it's relevant. The question is about to how implement the `is_callable` template, not how to use it to restrict my operator&& to certain types. – HighCommander4 Feb 24 '11 at 05:31
  • @HighCommander4: Yeah I guess D wasn't too relevant to C++; sorry. :\ – user541686 Feb 24 '11 at 05:33
  • 1
    @HighCommander4: a templated `operator()` isn't even the worst, think of multiple overloads and SFINAE applied to some... – Matthieu M. Feb 24 '11 at 07:30
  • 2
    Here is a C++03 version: http://ideone.com/ur90o . Of course it only works for a specific set of argument types, but it works with any callable entities. That is, including function pointers, references, and class types with a surrogate call function. I assert that it's impossible to detect callability if you have no clue about what you are going to push in. What if the class has an `op()` that has an `enable_if` that makes it only accept a single specific class type? Simulated argument types won't do it. – Johannes Schaub - litb Feb 26 '11 at 07:08

3 Answers3

20

The presence of a non-templated T::operator() for a given type T can be detected by:

template<typename C> // detect regular operator()
static char test(decltype(&C::operator()));

template<typename C> // worst match
static char (&test(...))[2];

static const bool value = (sizeof( test<T>(0)  )

The presence of a templated operator can be detected by:

template<typename F, typename A> // detect 1-arg operator()
static char test(int, decltype( (*(F*)0)( (*(A*)0) ) ) = 0);

template<typename F, typename A, typename B> // detect 2-arg operator()
static char test(int, decltype( (*(F*)0)( (*(A*)0), (*(B*)0) ) ) = 0);

// ... detect N-arg operator()

template<typename F, typename ...Args> // worst match
static char (&test(...))[2];

static const bool value = (sizeof( test<T, int>(0)  ) == 1) || 
                          (sizeof( test<T, int, int>(0)  ) == 1); // etc...

However, these two do not play nicely together, as decltype(&C::operator()) will produce an error if C has a templated function call operator. The solution is to run the sequence of checks against a templated operator first, and check for a regular operator() if and only if a templated one can not be found. This is done by specializing the non-templated check to a no-op if a templated one was found.

template<bool, typename T>
struct has_regular_call_operator
{
  template<typename C> // detect regular operator()
  static char test(decltype(&C::operator()));

  template<typename C> // worst match
  static char (&test(...))[2];

  static const bool value = (sizeof( test<T>(0)  ) == 1);
};

template<typename T>
struct has_regular_call_operator<true,T>
{
  static const bool value = true;
};

template<typename T>
struct has_call_operator
{
  template<typename F, typename A> // detect 1-arg operator()
  static char test(int, decltype( (*(F*)0)( (*(A*)0) ) ) = 0);

  template<typename F, typename A, typename B> // detect 2-arg operator()
  static char test(int, decltype( (*(F*)0)( (*(A*)0), (*(B*)0) ) ) = 0);

  template<typename F, typename A, typename B, typename C> // detect 3-arg operator()
  static char test(int, decltype( (*(F*)0)( (*(A*)0), (*(B*)0), (*(C*)0) ) ) = 0);

  template<typename F, typename ...Args> // worst match
  static char (&test(...))[2];

  static const bool OneArg = (sizeof( test<T, int>(0)  ) == 1);
  static const bool TwoArg = (sizeof( test<T, int, int>(0)  ) == 1);
  static const bool ThreeArg = (sizeof( test<T, int, int, int>(0)  ) == 1);

  static const bool HasTemplatedOperator = OneArg || TwoArg || ThreeArg;
  static const bool value = has_regular_call_operator<HasTemplatedOperator, T>::value;
};

If the arity is always one, as discussed above, then the check should be simpler. I do not see the need for any additional type traits or library facilities for this to work.

decltype
  • 1,591
  • 8
  • 12
  • Hmm.. In retrospect, I suppose those templated checks could simply use int instead... – decltype Feb 26 '11 at 15:42
  • This typedef lies if the functionoid's operator() can't be instantiated with `int`, but I don't think anything better can be made. – Mooing Duck Jan 14 '14 at 01:39
15

With the advent of our collective experience in c++11 (and beyond) it's probably time to revisit this question.

This little type trait seems to work for me:

#include <iostream>
#include <utility>

template<class F, class...Args>
struct is_callable
{
    template<class U> static auto test(U* p) -> decltype((*p)(std::declval<Args>()...), void(), std::true_type());
    template<class U> static auto test(...) -> decltype(std::false_type());

    static constexpr bool value = decltype(test<F>(0))::value;
};

Which we can test thus:

template<class F, class...Args, typename std::enable_if<is_callable<F, Args&&...>::value>::type* = nullptr>
void test_call(F, Args&&...args)
{
    std::cout << "callable" << std::endl;
}

template<class F, class...Args, typename std::enable_if<not is_callable<F, Args&&...>::value>::type* = nullptr>
void test_call(F, Args&&...args)
{
    std::cout << "not callable" << std::endl;
}

extern void f3(int, const std::string&)
{

}

int main()
{
    auto f1 = [](int, std::string) {};
    test_call(f1, 0, "hello");
    test_call(f1, "bad", "hello");

    std::function<void(int, const std::string&)> f2;
    test_call(f2, 0, "hello");
    test_call(f2, "bad", "hello");

    test_call(f3, 0, "hello");
    test_call(f3, "bad", "hello");

}

expected output:

callable
not callable
callable
not callable
callable
not callable
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • Richard, nice reply. What is the purpose of adding `void() in your first `test` statement? Can we eliminate it? – Jes Jul 14 '16 at 01:43
  • @Jes it's there in case someone has defined an overload of the comma operator, which could create a corner case where the trait incorrectly deduces that the object is not callable. Safer to leave it in. – Richard Hodges Jul 14 '16 at 05:56
  • Nice solution! I can't get it to work in my case though. Any ideas of what might I be doing wrong? https://pastebin.com/trD7vyj7 – Carles Araguz Jul 09 '18 at 18:06
11

This is a really interesting question. I've been puzzled hard by it.

I think I managed to make a variation to Crazy Eddie's code that will allow any number of parameters, however, it does use variadic templates and it does require to specify the parameters you are expecting the "callable" object to be called with. Long story short, I got this running and working as expected on gcc 4.6.0:

EDIT: One could use the std::result_of utility as well, however it does not work because it requires a typename to disambiguate the std::result_of<..>::type which breaks the Sfinae.

#include <iostream>
#include <type_traits>

template < typename PotentiallyCallable, typename... Args>
struct is_callable
{
  typedef char (&no)  [1];
  typedef char (&yes) [2];

  template < typename T > struct dummy;

  template < typename CheckType>
  static yes check(dummy<decltype(std::declval<CheckType>()(std::declval<Args>()...))> *);
  template < typename CheckType>
  static no check(...);

  enum { value = sizeof(check<PotentiallyCallable>(0)) == sizeof(yes) };
};

int f1(int,double) { return 0; };
typedef int(*f1_type)(int,double) ; //this is just to have a type to feed the template.

struct Foo { };

struct Bar {
  template <typename T>
  void operator()(T) { };
};

int main() {
  if( is_callable<f1_type,int,double>::value )
    std::cout << "f1 is callable!" << std::endl;
  if( is_callable<Foo>::value )
    std::cout << "Foo is callable!" << std::endl;
  if( is_callable<Bar,int>::value )
    std::cout << "Bar is callable with int!" << std::endl;
  if( is_callable<Bar,double>::value )
    std::cout << "Bar is callable with double!" << std::endl;
};

I hope this is what you are looking for because I don't think it is possible to do much more.

EDIT: For your use case, it is a partial solution, but it might help:

template <typename Predicate1, typename Predicate2>
struct and_predicate
{
    template <typename ArgT>
    enable_if<ice_and<is_callable<Predicate1,ArgT>::value,
                      is_callable<Predicate2,ArgT>::value>::value,
                      bool>::type operator()(const ArgT& arg)
    {
        return predicate1(arg) && predicate2(arg);
    }

    Predicate1 predicate1;
    Predicate2 predicate2;
};

template <typename Predicate1, typename Predicate2>
enable_if<ice_and<is_callable< Predicate1, boost::any >::value,
                  is_callable< Predicate2, boost::any >::value>::value,
                  and_predicate<Predicate1, Predicate2>>::type
operator&&(Predicate1 predicate1, Predicate2 predicate2)
{
    return and_predicate<Predicate1, Predicate2>{predicate1, predicate2};
}
Mikael Persson
  • 18,174
  • 6
  • 36
  • 52
  • Note C++0x already has the core of this as `std::result_of`, though I can't seem to find it in my GCC 4.5 install... – GManNickG Feb 24 '11 at 05:13
  • That's a nice attempt. Unfortunately, it still doesn't do what I need, because I don't know the parameters I expect to call it with (in the case of my operator&&, A or B may themselves have a templated operator(), so I cannot deduce their parameter types). – HighCommander4 Feb 24 '11 at 05:18
  • 1
    @GMan, yes I know about result_of, I should have pointed it out, but it also isn't available on gcc 4.6, at least it seems so. @HighCommander4: I think you are out of luck then, I don't see how you could check if a type is callable if you don't even know how you want to call it. Why would you? I really don't get it. You should maybe post a clearer exposition of the usage you need this for. – Mikael Persson Feb 24 '11 at 05:18
  • @Mikael: Yeah. That's strange because [this answer](http://stackoverflow.com/questions/2689709/difference-between-stdresult-of-and-decltype/2689761#2689761) seems to contradict our own findings. – GManNickG Feb 24 '11 at 05:20
  • @GMan, I found the result_of, and made it work, see my revision of the code. – Mikael Persson Feb 24 '11 at 05:40
  • @Mikael: Ah, that's strange. It's suppose to be in ``. – GManNickG Feb 24 '11 at 05:46
  • @GMan, ok, I guess it was in type_traits after all, however, it doesn't seem to work when the program runs. And that, is weird. – Mikael Persson Feb 24 '11 at 05:50
  • @HighCommander4: I see your point, and I do see how it could be useful. But, unfortunately, I would say it is impossible. Maybe the use of Boost.Any could help if you at least know the arity that the callable function ought to have. – Mikael Persson Feb 24 '11 at 06:54
  • @Mikael I do know the arity it ought to have (it's a predicate, so its arity is 1). How will Boost.Any help me? – HighCommander4 Feb 24 '11 at 07:03
  • Any won't work. Any doesn't match any type but any and doesn't implicitly convert to anything. The entity in question would have to work correctly ON an any for the match to succeed. That doesn't help wrt knowing if or if not it works correctly on the types you WILL use it with. – Edward Strange Feb 24 '11 at 07:04
  • @Crazy Eddie: I generally agree, I'm not sure how this would behave and I guess you're right. As for not knowing "if or if not it works correctly on the types you WILL use it with", that is, of course, impossible until you actually have a type to use it with. So the use of boost::any (if it works or can be made to work) is only really a means to check the arity of the function, because that is all that is known, so that is all that can be checked for at the operator &&'s definition. That's why a completely good solution is impossible. – Mikael Persson Feb 24 '11 at 07:19
  • @Mikael I still don't follow... do you mean with Boost.Any I can check whether or not a type has an operator() with arity 1? If so, that would be fantastic! – HighCommander4 Feb 24 '11 at 07:26
  • @HighCommander4: On second thought, I'm pretty sure that the example for your use-case that I posted above is not going to work as is. However, I'm sure that Boost.Any could come in handy somehow. Maybe with some additional implicit conversion operators. This is not an easy puzzle to solve, that's for sure! – Mikael Persson Feb 24 '11 at 07:40
  • @Mikael: Perhaps we can make use of the following: if the functor's `operator()` is templated, it should be callable with Boost.Any (among other things). If not, `boost::function_types::parameter_types` should be able to deduce its argument type. – HighCommander4 Feb 24 '11 at 08:06
  • @Mikael: So it would be something like this: We want to decide if type `T` is callable. 1) Use `boost::function_types::parameter_types` to deduce the parameter type of `T`'s `operator()`. If this fails, then either (A) `T` has no `operator()`, or (B) `T` has a templated `operator()`. 2) Use your `is_callable` metafunction above to see if `T` is callable with a Boost.Any parameter (actually any type should suffice, like int). The key is whether we can capture (A) and (B) as substitution failures rather than hard errors... is that possible? – HighCommander4 Feb 24 '11 at 08:16
  • @Michael - just checking arrity and hoping it compiles for a type you'll never use with it probably won't work. Even though the expression is not being executed, just evaluated, I think the compiler has to instantiate the template. This means anything you might have wanted to do within the template had better be able to deal with the type you pass it. With that in mind, `void*` has more opportunity to work...though not much. You'd pretty much need to require the functor author to write a special operator(). Traits/flag fields would be better in that case. Most the std stuff has it anyway – Edward Strange Feb 24 '11 at 17:32