找出C ++对象是否可调用

问题描述 投票:30回答:8

是否有可能写一个类型特征,比如is_callable<T>,它告诉对象是否定义了operator()?如果调用运算符的参数事先已知,则很容易,但在一般情况下则不然。当且仅当至少有一个重载调用运算符被定义时,我希望特征返回true。

This question是相关的并且有一个很好的答案,但它不适用于所有类型(仅限于int-convertible类型)。此外,std::is_function工作,但只适用于正确的C ++函数,而不是函子。我正在寻找更通用的解决方案。

c++ metaprogramming traits typetraits
8个回答
33
投票

我认为这个特性可以满足您的需求。它会检测operator()的任何类型的签名,即使它过载也是如果它被模板化:

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

该原则基于Member Detector idiom。实际上,如果你传递一个非类型的类型,它将无法编译,但这不应该很难解决,我只是为了简洁而将它留下。您还可以将其扩展为报告函数的true。

当然它没有给你任何关于operator()签名的信息,但我相信这不是你要求的,对吧?

编辑Klaim

它非常简单,可以使用非类型类型(返回false)。如果将上面的类重命名为is_callable_impl,则可以编写此代码,例如:

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

10
投票

这里的答案很有帮助,但我来到这里想要的东西也可以发现某些东西是否可以调用,无论它是否恰好是一个对象或一个经典的函数。 jrok's answer对问题的这个方面,唉,没有用,因为std::conditional实际上评估了双臂的类型!

所以,这是一个解决方案:

// 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;

但最后,对于我的应用程序,我真的只想知道你是否可以说f()(即,没有参数调用它),所以我选择了更简单的东西。

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

事实上,我走得更远了。我知道该函数应该返回一个int,所以我只是检查我可以调用它,我检查了返回类型,通过将enable_if更改为:

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

希望这有助于某人!


9
投票

这是一个使用C ++ 11的可能解决方案,无需知道仿函数的调用操作符的签名即可工作,但只有仿函数没有多个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 { };

这是你如何使用它:

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");
}

3
投票

注意:这些假设默认构造函数对您检查的类型有效。不确定如何解决这个问题。

如果它可以用0参数调用,则以下似乎有效。 is_function的实现中是否存在可能有助于将其扩展为1个或多个参数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);
};   

如果您知道参数的类型(即使它是模板参数),以下内容适用于1个参数,我想可以从那里轻松扩展:

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

编辑here是一个修改,用于处理默认构造函数不可用的情况。


3
投票

当然,还有其他几个答案,它们很有用,但它们似乎都没有覆盖每个用例AFAICT。借用这些答案和this possible implementation of std::is_function,我创建了一个涵盖我能想到的每个可能用例的版本。它有点冗长,但非常完整(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);
};

这适用于非类类型(当然返回false),函数类型(<T()>),函数指针类型,函数引用类型,仿函数类类型,绑定表达式,lambda类型等。这甚至可以正常工作如果类构造函数是私有的和/或非默认的,即使operator()被重载。这对于成员函数指针的设计返回false,因为它们不可调用,但您可以使用bind来创建可调用的表达式。


1
投票

这是另一个实现。

它利用std::is_function模板实现免费功能。

对于类,它使用类似于Member Detector Idiom的东西。如果T有一个调用运算符,callable_2将包含多个operator()。由于can_call的模糊性失败,这将导致第一个decltype(&callable_2<T>::operator())函数被丢弃(通过SFINAE),第二个can_call函数将返回true。如果T没有调用操作符,将使用第一个can_call函数(由于重载决策规则)。

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

1
投票

这是一个简洁明了的技巧,用于查找T是否可调用。它遵循Walter E. Brown最初在CPPCON'14中提出的关于现代模板元编程的讨论。

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

1
投票

C ++ 17带来了std::is_invocable和朋友。

This answer还给出了如何用C ++ 14模拟它的解决方案。

© www.soinside.com 2019 - 2024. All rights reserved.