C++ Metafunction to Determine Whether a Type Is Callable

C++ metafunction to determine whether a type is callable

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.

C++ meta function that determines if a type is callable for supplied arguments

The shorten use of std::result_of to do what you want could look as follows:

template <class T, class, class... Args>
struct callable: std::false_type {
};

template <class T, class... Args>
struct callable<T, decltype(std::result_of_t<T(Args...)>(), void()), Args...>:std::true_type {
};

template <class F, class... Args>
constexpr auto callable_v = callable<F, void, Args...>::value;

[live demo]

you need to remember that type returned by result_of is always the result type of a function you pass to this trait by type. To let your sfinae work you need a method to change this type to void in every possible situation. You can accomplish it by using the trick with decltype (decltype(std::result_of_t<T(Args...)>(), void())).

Edit:

To elaborate the thread from comments about the possible drawbacks of the solution. The std::result_of_t<T(Args...)> type don't need to be equipped with a default non-parametric constructor and as such the sfinae may cause false negative result of callable_v for function that result in this kind of types. In comments I proposed a workaround for the issue that does not really solve the problem or actually generate a new one:

decltype(std::declval<std::result_of_t<T(Args...)>*>(), void())

Intention of this code was to make sfinae work as in previously proposed solution but in case of non-constructable types to create an easy to construct (I thought) object of pointer to given type... In this reasoning I didn't take into consideration the types that one cannot create a pointer to e.g. references. This one again can be workaround by using some additional wrapper class:

decltype(std::declval<std::tuple<std::result_of_t<T(Args...)>>*>(), void())

or by decaying the result type:

decltype(std::declval<std::decay_t<std::result_of_t<T(Args...)>>*>(), void())

but I think it might not be worth it and maybe use of void_t is actually a more straightforward solution:

template <class...>
struct voider {
using type = void;
};

template <class... Args>
using void_t = typename voider<Args...>::type;

template <class T, class, class... Args>
struct callable: std::false_type {
};

template <class T, class... Args>
struct callable<T, void_t<std::result_of_t<T(Args...)>>, Args...>:std::true_type {
};

template <class F, class... Args>
constexpr auto callable_v = callable<F, void, Args...>::value;

[live demo]

Find out whether a C++ object is callable

I think this trait does what you want. It detects operator() with any kind of signature even if it's overloaded and also if it's templatized:

template<typename T>
struct is_callable {
private:
typedef char(&yes)[1];
typedef char(&no)[2];

struct Fallback { void operator()(); };
struct Derived : T, Fallback { };

template<typename U, U> struct Check;

template<typename>
static yes test(...);

template<typename C>
static no test(Check<void (Fallback::*)(), &C::operator()>*);

public:
static const bool value = sizeof(test<Derived>(0)) == sizeof(yes);
};

The principle is based on Member Detector idiom. As it is, it will fail to compile if you pass it a non-class type, but that shouldn't be hard to fix, I just left it out for brevity. You can also extend it to report true for functions.

Of course it doesn't give you any info about the signature(s) of operator() whatsoever, but I believe that's not what you asked for, right?

EDIT for Klaim:

It's simple enough to make it work (return false) with non-class types. If you rename the above class to is_callable_impl, you can write this, for example:

template<typename T>
struct is_callable
: std::conditional<
std::is_class<T>::value,
is_callable_impl<T>,
std::false_type
>::type
{ };

What is the correct way to check whether a template argument is a true callable object?

You're going to need to do some kind of specialization or overloading, but you can at least separate out the work into a predicate function:

template<class T>
typename std::enable_if<
std::is_constructible<bool, T>::value, bool>::type
okToCall(T&&t) { return static_cast<bool>(t); }
template<class T>
constexpr typename std::enable_if<
!std::is_constructible<bool, T>::value, bool>::type
okToCall(T&&) { return true; }

template<class T> void fun(T t) {
if (okToCall(t)) std::cout << t();
else std::cout << "no t";
}

Example: http://coliru.stacked-crooked.com/a/a08468965ed6d54e

The only real difficulty is working out what to call the okToCall predicate function - what it's really doing is returning true if T is not convertible to bool, but its value converted to bool if it is convertible.

How do I determine if a type is callable with only const references?

After hours of playing around and some serious discussions in the C++ chat room, we finally got a version that works for functors with possibly overloaded or inherited operator() and for function pointers, based on @KerrekSB's and @BenVoigt's versions.

#include <utility>
#include <type_traits>

template <typename F, typename... Args>
class Callable{
static int tester[1];
typedef char yes;
typedef yes (&no)[2];

template <typename G, typename... Brgs, typename C>
static typename std::enable_if<!std::is_same<G,C>::value, char>::type
sfinae(decltype(std::declval<G>()(std::declval<Brgs>()...)) (C::*pfn)(Brgs...));

template <typename G, typename... Brgs, typename C>
static typename std::enable_if<!std::is_same<G,C>::value, char>::type
sfinae(decltype(std::declval<G>()(std::declval<Brgs>()...)) (C::*pfn)(Brgs...) const);

template <typename G, typename... Brgs>
static char sfinae(decltype(std::declval<G>()(std::declval<Brgs>()...)) (G::*pfn)(Brgs...));

template <typename G, typename... Brgs>
static char sfinae(decltype(std::declval<G>()(std::declval<Brgs>()...)) (G::*pfn)(Brgs...) const);

template <typename G, typename... Brgs>
static yes test(int (&a)[sizeof(sfinae<G,Brgs...>(&G::operator()))]);

template <typename G, typename... Brgs>
static no test(...);

public:
static bool const value = sizeof(test<F, Args...>(tester)) == sizeof(yes);
};

template<class R, class... Args>
struct Helper{ R operator()(Args...); };

template<typename R, typename... FArgs, typename... Args>
class Callable<R(*)(FArgs...), Args...>
: public Callable<Helper<R, FArgs...>, Args...>{};

Live example on Ideone. Note that the two failing tests are overloaded operator() tests. This is a GCC bug with variadic templates, already fixed in GCC 4.7. Clang 3.1 also reports all tests as passed.

If you want operator() with default arguments to fail, there is a possible way to do that, however some other tests will start failing at that point and I found it as too much hassle to try and correct that.

Edit: As @Johannes correctly notes in the comment, we got a little inconsistency in here, namely that functors which define a conversion to function pointer will not be detected as "callable". This is, imho, pretty non-trivial to fix, as such I won't bother with it (for now). If you absolutely need this trait, well, leave a comment and I'll see what I can do.


Now that all this has been said, IMHO, the idea for this trait is stupid. Why whould you have such exact requirements? Why would the standard is_callable not suffice?

(Yes, I think the idea is stupid. Yes, I still went and built this. Yes, it was fun, very much so. No, I'm not insane. Atleast that's what I believe...)

How to detect whether some callable takes a rvalue reference?

This should work for most sane lambdas (and by extension, things that are sufficiently like lambdas):

struct template_rref {};
struct template_lref {};
struct template_val {};

struct normal_rref{};
struct normal_lref{};
struct normal_val{};

template<int R> struct rank : rank<R-1> { static_assert(R > 0, ""); };
template<> struct rank<0> {};

template<class F, class A>
struct first_arg {

using return_type = decltype(std::declval<F>()(std::declval<A>()));
using arg_type = std::decay_t<A>;

static template_rref test(return_type (F::*)(arg_type&&), rank<5>);
static template_lref test(return_type (F::*)(arg_type&), rank<4>);
static template_lref test(return_type (F::*)(const arg_type&), rank<3>);
static template_val test(return_type (F::*)(arg_type), rank<6>);

static template_rref test(return_type (F::*)(arg_type&&) const, rank<5>);
static template_lref test(return_type (F::*)(arg_type&) const, rank<4>);
static template_lref test(return_type (F::*)(const arg_type&) const, rank<3>);
static template_val test(return_type (F::*)(arg_type) const, rank<6>);

template<class T>
static normal_rref test(return_type (F::*)(T&&), rank<12>);
template<class T>
static normal_lref test(return_type (F::*)(T&), rank<11>);
template<class T>
static normal_val test(return_type (F::*)(T), rank<10>);

template<class T>
static normal_rref test(return_type (F::*)(T&&) const, rank<12>);
template<class T>
static normal_lref test(return_type (F::*)(T&) const, rank<11>);
template<class T>
static normal_val test(return_type (F::*)(T) const, rank<10>);

using result = decltype(test(&F::operator(), rank<20>()));
};

"sane" = no crazy stuff like const auto&& or volatile.

rank is used to help manage overload resolution - the viable overload with the highest rank is selected.

First consider the highly-ranked test overloads that are function templates. If F::operator() is a template, then the first argument is a non-deduced context (by [temp.deduct.call]/p6.1), and so T cannot be deduced, and they are removed from overload resolution.

If F::operator() isn't a template, then deduction is performed, the appropriate overload is selected, and the type of the first parameter is encoded in the function's return type. The ranks effectively establish an if-else-if relationship:

  • If the first argument is an rvalue reference, deduction will succeed for one of the two rank 12 overloads, so it's chosen;
  • Otherwise, deduction will fail for the rank 12 overloads. If the first argument is an lvalue reference, deduction will succeed for one of the rank 11 overloads, and that one is chosen;
  • Otherwise, the first argument is by value, and deduction will succeed for the rank 10 overload.

Note that we leave rank 10 last because deduction will always succeed for that one regardless of the nature of the first argument - it can deduce T as a reference type. (Actually, we'd get the right result if we made the six template overloads all have the same rank, due to partial ordering rules, but IMO it's easier to understand this way.)

Now to the lowly-ranked test overloads, which have hard-coded pointer-to-member-function types as their first parameter. These are only really in play if F::operator() is a template (if it isn't then the higher-ranked overloads will prevail). Passing the address of a function template to these functions causes template argument deduction to be performed for that function template to obtain a function type that matches the parameter type (see [over.over]).

We consider the [](auto){}, [](auto&){}, [](const auto&){} and [](auto&&){} cases. The logic encoded in the ranks is as follows:

  • If the function template can be instantiated to take a non-reference arg_type, then it must be (auto) (rank 6);
  • Else, if the function template can be instantiated to something taking an rvalue reference type arg_type&&, then it must be (auto&&) (rank 5);
  • Else, if the function template can be instantiated to something taking a non-const-qualified arg_type&, then it must be (auto&) (rank 4);
  • Else, if the function template can be instantiated to something taking a const arg_type&, then it must be (const auto&) (rank 3).

Here, again, we handle the (auto) case first because otherwise it can be instantiated to form the three other signatures as well. Moreover, we handle the (auto&&) case before the (auto&) case because for this deduction the forwarding reference rules apply, and auto&& can be deduced from arg_type&.

What is the best type for a callable object in a template method?

In general, I do not like passing callable objects by const reference, because it is not that flexible (e.g. it cannot be used on mutable lambdas). I suggest to pass them by value. If you check the stl algorithms implementation, (e.g. for std::for_each), all of the callable objects are passed by value as well.

Doing this, the users are still able to use std::ref(func) or std::cref(func) to avoid unnecessary copying of the callable object (using reference_wrapper), if desired.

Check a type is a functor including generic lambda

There is no proper way of doing this (at least until we get static reflection). The best you can do is check that an object is callable with a certain degree of confidence:

  1. Try getting its operator() address. If it fails, then the object may either be non-callable or its operator() could be overloaded/templated.

  2. Try calling the object with a dummy any_type instance that provides an interface for commonly used function. This might help you deduce its arity.

  3. If everything fails, force the user to somehow help the arity deduction or manually specify the arity.

One way you could approach this is by having a deduced_arity set of tags:

namespace deduced_arity
{
template <std::size_t TS>
struct deducible_t : std::integral_constant<std::size_t, TS>
{
};

struct undeducible_t
{
};

constexpr undeducible_t undeducible{};
constexpr deducible_t<1> unary{};
}

You will also need some sort of function_traits implementation that will statically tell you the exact arity of a function object. This can be found in Boost.

Then you also need an implementation of any_type.

Afterwards, you can use something like the following type trait to check whether or not a function object may be overloaded, using the detection idiom:

template <typename T>
using is_not_overloaded_impl = decltype(&T::operator());

template <typename T>
using is_not_overloaded =
std::experimental::is_detected<is_not_overloaded_impl, T>;

Then you can use a chain of if constexpr(...) (or any other compile-time branching mechanism) to make a good guess - example:

if constexpr(is_not_overloaded<T>{})
{
// use `function_traits` here
}
else if constexpr(std::is_callable<T(any_type)>{})
{
return deduced_arity::unary;
}
else if constexpr(/* user manually marked arity */)
{
/* deal with user-defined deduction helpers */
}
else
{
return deduced_arity::undeducible;
}


Related Topics



Leave a reply



Submit