Find out whether a C++ object is callable

不打扰是莪最后的温柔 提交于 2019-11-26 17:30:27
jrok

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
{ };

The answers here were helpful but I came here wanting something that could also spot whether something was callable regardless of whether it happened to be an object or a classic function. jrok's answer to this aspect of the problem, alas, didn't work because std::conditional actually evaluates the types of both arms!

So, here's a solution:

// Note that std::is_function says that pointers to functions
// and references to functions aren't functions, so we'll make our 
// own is_function_t that pulls off any pointer/reference first.

template<typename T>
using remove_ref_t = typename std::remove_reference<T>::type;

template<typename T>
using remove_refptr_t = typename std::remove_pointer<remove_ref_t<T>>::type;

template<typename T>
using is_function_t = typename std::is_function<remove_refptr_t<T>>::type;

// We can't use std::conditional because it (apparently) must determine
// the types of both arms of the condition, so we do it directly.

// Non-objects are callable only if they are functions.

template<bool isObject, typename T>
struct is_callable_impl : public is_function_t<T> {};

// Objects are callable if they have an operator().  We use a method check
// to find out.

template<typename T>
struct is_callable_impl<true, T> {
private:
    struct Fallback { void operator()(); };
    struct Derived : T, Fallback { };

    template<typename U, U> struct Check;

    template<typename>
    static std::true_type test(...);

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

public:
    typedef decltype(test<Derived>(nullptr)) type;
};


// Now we have our final version of is_callable_t.  Again, we have to take
// care with references because std::is_class says "No" if we give it a
// reference to a class.

template<typename T>
using is_callable_t = 
    typename is_callable_impl<std::is_class<remove_ref_t<T>>::value,
                              remove_ref_t<T> >::type;

But in the end, for my application, I really wanted to just know whether you could say f() (i.e., call it with no arguments), so I instead went with something much simpler.

template <typename T>
constexpr bool noarg_callable_impl(
    typename std::enable_if<bool(sizeof((std::declval<T>()(),0)))>::type*)
{
    return true;
}

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

template<typename T>
constexpr bool is_noarg_callable()
{
    return noarg_callable_impl<T>(nullptr);
}

In fact, I went even further. I knew the function was supposed to return an int, so rather than just check that I could call it, I checked the return type, too, by changing the enable_if to:

    typename std::enable_if<std::is_convertible<decltype(std::declval<T>()()),
                                                int>::value>::type*)

Hope this helps someone!

Andy Prowl

Here is a possible solution using C++11 that works without requiring to know the signature of the call operator for functors, but only as long the functor does not have more than one overload of operator ():

#include <type_traits>

template<typename T, typename = void>
struct is_callable : std::is_function<T> { };

template<typename T>
struct is_callable<T, typename std::enable_if<
    std::is_same<decltype(void(&T::operator())), void>::value
    >::type> : std::true_type { };

This is how you would use it:

struct C
{
    void operator () () { }
};

struct NC { };

struct D
{
    void operator () () { }
    void operator () (int) { }
};

int main()
{
    static_assert(is_callable<C>::value, "Error");
    static_assert(is_callable<void()>::value, "Error");

    auto l = [] () { };
    static_assert(is_callable<decltype(l)>::value, "Error");

    // Fires! (no operator())
    static_assert(is_callable<NC>::value, "Error");

    // Fires! (several overloads of operator ())
    static_assert(is_callable<D>::value, "Error");
}

Note: These assume that the default constructor is valid for the type your checking. Not sure offhand how to get around that.

The following seems to work if it's callable with 0 arguments. Is there something in is_function's implementation that might help to extend this to 1 or more argument callables?:

template <typename T>
struct is_callable {
    // Types "yes" and "no" are guaranteed to have different sizes,
    // specifically sizeof(yes) == 1 and sizeof(no) == 2.
    typedef char yes[1];
    typedef char no[2];

    template <typename C>
    static yes& test(decltype(C()())*);

    template <typename>
    static no& test(...);

    // If the "sizeof" the result of calling test<T>(0) would be equal to the     sizeof(yes),
    // the first overload worked and T has a nested type named foobar.
    static const bool value = sizeof(test<T>(0)) == sizeof(yes);
};   

If you know the type of the argument (even if it's a template parameter), the following would work for 1 argument, and I imagine one could extend pretty easily from there:

template <typename T, typename T2>
struct is_callable_1 {
    // Types "yes" and "no" are guaranteed to have different sizes,
    // specifically sizeof(yes) == 1 and sizeof(no) == 2.
    typedef char yes[1];
    typedef char no[2];

    template <typename C>
    static yes& test(decltype(C()(T2()))*);

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

    // If the "sizeof" the result of calling test<T>(0) would be equal to the     sizeof(yes),
    // the first overload worked and T has a nested type named foobar.
    static const bool value = sizeof(test<T>(0)) == sizeof(yes);
};

Edit here is a modification that handles the case where default constructor isn't available.

There are several other answers already, of course, and they are useful, but none of them seem to cover every use case AFAICT. Borrowing from those answers and this possible implementation of std::is_function, I created a version that covers every possible use case of which I could think. It's kind of lengthy, but very feature complete (Demo).

template<typename T, typename U = void>
struct is_callable
{
    static bool const constexpr value = std::conditional_t<
        std::is_class<std::remove_reference_t<T>>::value,
        is_callable<std::remove_reference_t<T>, int>, std::false_type>::value;
};

template<typename T, typename U, typename ...Args>
struct is_callable<T(Args...), U> : std::true_type {};
template<typename T, typename U, typename ...Args>
struct is_callable<T(*)(Args...), U> : std::true_type {};
template<typename T, typename U, typename ...Args>
struct is_callable<T(&)(Args...), U> : std::true_type {};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args......), U> : std::true_type {};
template<typename T, typename U, typename ...Args>
struct is_callable<T(*)(Args......), U> : std::true_type {};
template<typename T, typename U, typename ...Args>
struct is_callable<T(&)(Args......), U> : std::true_type {};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args...)const, U> : std::true_type {};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args...)volatile, U> : std::true_type {};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args...)const volatile, U> : std::true_type {};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args......)const, U> : std::true_type {};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args......)volatile, U> : std::true_type{};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args......)const volatile, U> : std::true_type {};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args...)&, U> : std::true_type {};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args...)const&, U> : std::true_type{};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args...)volatile&, U> : std::true_type{};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args...)const volatile&, U> : std::true_type{};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args......)&, U> : std::true_type {};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args......)const&, U> : std::true_type{};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args......)volatile&, U> : std::true_type{};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args......)const volatile&, U> : std::true_type{};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args...)&&, U> : std::true_type{};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args...)const&&, U> : std::true_type{};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args...)volatile&&, U> : std::true_type{};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args...)const volatile&&, U> : std::true_type{};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args......)&&, U> : std::true_type{};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args......)const&&, U> : std::true_type{};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args......)volatile&&, U> : std::true_type{};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args......)const volatile&&, U> : std::true_type{};

template<typename T>
struct is_callable<T, int>
{
private:
    using YesType = char(&)[1];
    using NoType = char(&)[2];

    struct Fallback { void operator()(); };

    struct Derived : T, Fallback {};

    template<typename U, U>
    struct Check;

    template<typename>
    static YesType Test(...);

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

public:
    static bool const constexpr value = sizeof(Test<Derived>(0)) == sizeof(YesType);
};

This works correctly with non-class types (returns false, of course), function types (<T()>), function pointer types, function reference types, functor class types, bind expressions, lambda types, etc. This works correctly even if the class constructor is private and/or non-defaulted, and even if operator() is overloaded. This returns false for member function pointers by design because they are not callable, but you can use bind to create a callable expression.

Here is another implementation.

It makes use of std::is_function template for free functions.

For classes, it uses something similar to the Member Detector Idiom. If T has a call operator, callable_2 will contain more than one operator(). This will cause the first can_call function to be discarded (through SFINAE) due to ambiguity failure in decltype(&callable_2<T>::operator()) and the second can_call function will return true. If T does not have a call operator, the first can_call function will be used (due to overload resolution rules).

namespace impl
{
struct callable_1 { void operator()(); };
template<typename T> struct callable_2 : T, callable_1 { };

template<typename T>
static constexpr bool can_call(decltype(&callable_2<T>::operator())*) noexcept { return false; }

template<typename>
static constexpr bool can_call(...) noexcept { return true; }

template<bool is_class, typename T>
struct is_callable : public std::is_function<T> { };

template<typename T> struct is_callable<false, T*> : public is_callable<false, T> { };
template<typename T> struct is_callable<false, T* const> : public is_callable<false, T> { };
template<typename T> struct is_callable<false, T* volatile> : public is_callable<false, T> { };
template<typename T> struct is_callable<false, T* const volatile> : public is_callable<false, T> { };

template<typename T>
struct is_callable<true, T> : public std::integral_constant<bool, can_call<T>(0)> { };
}

template<typename T>
using is_callable = impl::is_callable<std::is_class<std::remove_reference_t<T>>::value,
                                    std::remove_reference_t<T>>;

This is a neat and short trick for finding if T is callable. It goes along the lines originally proposed by Walter E. Brown at CPPCON'14 in his talk on modern template metaprogramming.

template <class... >
using void_t = void;

template <class T>
using has_opr_t = decltype(&T::operator());

template <class T, class = void>
struct is_callable : std::false_type { };

template <class T>
struct is_callable<T, void_t<has_opr_t<typename std::decay<T>::type>>> : std::true_type { };

C++17 brings std::is_invocable and friends.

This answer also given a solution on how to emulate it with C++14.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!