1

I use the following way, presented in a stackoverflow answer to determine whether a type is callable or not:

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

    // we need a template here to enable SFINAE
    template <typename U> 
    static yes deduce(char (*)[sizeof(&U::operator())]);
    // fallback
    template <typename> static no deduce(...);

    static bool constexpr value = std::is_function<T>::value || (sizeof(deduce<T>(0)) == sizeof(yes));
};

However it fails when a class has more than one overload of operator():

#include <iostream>

template <typename T>
class A
{
    T operator()();
    T operator()(T a);
    T operator()(T a, T b);
};

std::cout << is_callable<A<int>>::value; // false

Is it possible to determine whether a type has any variant (templated or not, one or multiple) of overloaded operator()?

plasmacel
  • 8,183
  • 7
  • 53
  • 101
  • How do you intend to make use of this information? In most cases, you don't actually care whether it's callable, you only care whether it's callable with the specific arguments you want to pass to it, and that's already easily detectable. –  Oct 13 '15 at 20:19
  • 1
    The reason I asked is because it's not possible, but depending on what you're after specifically and what assumptions you can make on the types you'll be testing, something else might be possible. If you'll check my answer history you'll see that I do answer C++ questions, including questions about using templates to detect type traits. But if you're happier to think I'm out to ruin your experience, go ahead, I wouldn't want to be accused of trying to control your thoughts. –  Oct 13 '15 at 20:29
  • I would also be curious as to the desired use-case. At best, I could write something that check if a type was callable with up to N arguments, with some restrictions on what those arguments could end up being. – Barry Oct 13 '15 at 20:47
  • 1
    Simple and good. http://stackoverflow.com/a/15396757/4461183 – Karlis Olte Oct 13 '15 at 21:03
  • That's okay. Just a heads up about the link in @KarlisOlte's answer: that doesn't actually work for all cases. As mentioned in the comments there, it only works for types that can be derived from. Built-in types can get special treatment to make it work as an exception, if desired, but for final classes, you're out of luck. –  Oct 13 '15 at 22:28

2 Answers2

0

First of all you need to declare A as a full type (A<int> for example).

It's not relyable to check if an object is invokeable or not just through unwrapping and comparing it's operator() because it will fail on templated or overloaded functors.

Im using the following piece of code to detect if an object is callable with a given function signature (ReturnType(Arguments...))

This works also for overloaded or templated operator()'s since the code tries to invoke the object with the given parameters instead of comparing the signature of it's unwrapped operator().

As small addition the object can also be checked if it is invokeable from a const, volatile or rvalue context.

#include <type_traits>

template<typename Fn>
struct impl_is_callable_with_qualifiers;

template<typename ReturnType, typename... Args>
struct impl_is_callable_with_qualifiers<ReturnType(Args...)>
{
    template<typename T>
    static auto test(int)
        -> typename std::is_convertible<
            decltype(std::declval<T>()(std::declval<Args>()...)),
            ReturnType
           >;

    template<typename T>
    static auto test(...)
        -> std::false_type;
};

template<bool Condition, typename T>
using add_const_if_t = typename std::conditional<
    Condition,
    typename std::add_const<T>::type,
    T
>::type;

template<bool Condition, typename T>
using add_volatile_if_t = typename std::conditional<
    Condition,
    typename std::add_volatile<T>::type,
    T
>::type;

template<bool Condition, typename T>
using add_lvalue_if_t = typename std::conditional<
    Condition,
    typename std::add_lvalue_reference<T>::type,
    T
>::type;

template<typename T, typename Fn, bool Const, bool Volatile, bool RValue>
using is_callable_with_qualifiers = decltype(impl_is_callable_with_qualifiers<Fn>::template test<
    add_lvalue_if_t<!RValue,
        add_volatile_if_t<Volatile,
            add_const_if_t<Const,
                typename std::decay<T>::type>>>
>(0));

It is not possible to determine whether a type has any variant of overloaded operator(), however you can test for a specific type or perform tests for multiple types to detect "possible" templates.

An example which covers the code from your question would be:

#include <iostream>

template<typename T>
class A
{
public:
    A() = default;

    T operator()() { return T(); }
    T operator()(T a) { return T(); }
    T operator()(T a, T b) { return T(); }
};

template <typename TheClass, typename T>
using is_callable = is_callable_with_qualifiers<TheClass, T(T), false, false, false>;

int main()
{
    std::cout << is_callable<A<int>, int>::value; // true
}

Demo

Naios
  • 1,513
  • 1
  • 12
  • 26
  • I suggested that this might be good enough in a comment on the question. The OP was not happy with my comment at all. –  Oct 13 '15 at 20:30
0

If you just need to check if smth is callable - try to call it and see what happens:

template <typename T, typename RetType = decltype(std::declval<T>()())>
constexpr bool check_is_callable(int)
{
    return true;
}

template <typename>
constexpr bool check_is_callable(...)
{
    return false;
}

template <typename T>
constexpr bool is_callable()
{
    return check_is_callable<T>(0);
}

Here std::declval<T>() is getting the object of T type, std::declval<T>()() is attempt to call the object of T type, and if it succeeds, RetType = decltype(std::declval<T>()()) gets the return type of call. If it does not, SFIANE lets drop to the other function overload.

Lol4t0
  • 12,444
  • 4
  • 29
  • 65