C++ metafunction to determine whether a type is callable

HighCommander4 picture HighCommander4 · Feb 24, 2011 · Viewed 8.7k times · Source

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.

Answer

decltype picture decltype · Feb 25, 2011

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.