Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ metafunction to determine whether a type is callable

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.

like image 453
HighCommander4 Avatar asked Feb 24 '11 03:02

HighCommander4


2 Answers

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 
like image 34
Richard Hodges Avatar answered Sep 24 '22 13:09

Richard Hodges


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.

like image 169
decltype Avatar answered Sep 22 '22 13:09

decltype