是否可以编写类型特征,比如is_callable<T>
告诉对象是否已operator()
定义?如果事先知道调用运算符的参数很容易,但不是在一般情况下。当且仅当至少定义了一个重载调用运算符时,我希望特征返回 true。
这个问题是相关的并且有一个很好的答案,但它不适用于所有类型(仅适用于int
-convertible 类型)。此外,std::is_function
有效,但仅适用于正确的 C++ 函数,而不适用于仿函数。我正在寻找更通用的解决方案。
是否可以编写类型特征,比如is_callable<T>
告诉对象是否已operator()
定义?如果事先知道调用运算符的参数很容易,但不是在一般情况下。当且仅当至少定义了一个重载调用运算符时,我希望特征返回 true。
这个问题是相关的并且有一个很好的答案,但它不适用于所有类型(仅适用于int
-convertible 类型)。此外,std::is_function
有效,但仅适用于正确的 C++ 函数,而不适用于仿函数。我正在寻找更通用的解决方案。
我认为这个特性可以满足您的需求。它检测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 成语。实际上,如果您将其传递给非类类型,它将无法编译,但这应该不难修复,为了简洁起见,我只是将其省略了。您还可以将其扩展为对函数报告 true。
当然它不会给你任何关于签名的信息operator()
,但我相信这不是你要求的,对吧?
编辑克莱姆:
让它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
{ };
这是使用 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");
}
这里的答案很有帮助,但我来到这里想要一些东西,它也可以发现某些东西是否是可调用的,无论它碰巧是一个对象还是一个经典函数。 唉, jrok对这方面问题的回答没有奏效,因为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*)
希望这对某人有帮助!
C++17 带来了std::is_invocable和朋友们。
这个答案还给出了如何用 C++14 模拟它的解决方案。
当然,已经有其他几个答案,它们很有用,但似乎没有一个能涵盖 AFAICT 的所有用例。借用这些答案和std::is_function 的这种可能实现,我创建了一个版本,涵盖了我能想到的所有可能的用例。它有点冗长,但功能非常完整(演示)。
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 来创建可调用表达式。
注意:这些假设默认构造函数对您检查的类型有效。不确定如何解决这个问题。
如果可以使用 0 个参数调用,则以下内容似乎有效。is_function 的实现中是否有一些东西可能有助于将其扩展到 1 个或多个参数可调用对象?:
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);
};
此处编辑是处理默认构造函数不可用的情况的修改。
这是查找 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 { };
这是另一个实现。
它利用std::is_function
模板实现免费功能。
对于类,它使用类似于Member Detector Idiom的东西。如果T
有一个呼叫运算符,callable_2
将包含多个operator()
. can_call
由于歧义失败,这将导致第一个函数被丢弃(通过 SFINAE) decltype(&callable_2<T>::operator())
,第二个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>>;