我该如何在静态情况下推断一个参数是否为C++函数对象(仿函数)?
template <typename F>
void test(F f) {}
我尝试了is_function<F>::value
,但它并没有起作用。似乎也没有is_functor
特性,所以可能不可能实现。在这种情况下,我只需要查找一个特定的成员函数,即函数调用运算符:F::operator()
。
我该如何在静态情况下推断一个参数是否为C++函数对象(仿函数)?
template <typename F>
void test(F f) {}
我尝试了is_function<F>::value
,但它并没有起作用。似乎也没有is_functor
特性,所以可能不可能实现。在这种情况下,我只需要查找一个特定的成员函数,即函数调用运算符:F::operator()
。
// build R (*)(Args...) from R (Args...)
// compile error if signature is not a valid function signature
template <typename, typename>
struct build_free_function;
template <typename F, typename R, typename ... Args>
struct build_free_function<F, R (Args...)>
{ using type = R (*)(Args...); };
// determine whether a free function pointer F has signature S
template <typename F, typename S>
struct is_function_with_signature
{
// check whether F and the function pointer of S are of the same
// type
static bool constexpr value = std::is_same<
F, typename build_free_function<F, S>::type
>::value;
};
第二步:类函子
这一步有点复杂。我们可以使用SFINAE轻松地检测一个类是否定义了operator()
:
template <typename T>
struct defines_functor_operator
{
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 = sizeof(deduce<T>(0)) == sizeof(yes);
};
但这并不能告诉我们是否存在我们所需的函数签名!幸运的是,我们可以在这里使用一个技巧:指针是有效的模板参数。因此,我们可以首先使用所需签名的成员函数指针,并检查&T::operator()
是否为该类型:
template <typename T, T> struct check;
check<void (C::*)() const, &C::operator()>
只有在 C
确实拥有 void C::operator()() const
时才是有效的模板实例化。但是为了做到这一点,我们首先必须将 C
和签名组合成一个成员函数指针。正如我们已经看到的,我们需要关注两个额外的情况,这些情况对于自由函数来说我们不必担心: const
和 volatile
函数。除此之外,它基本上是相同的。// build R (C::*)(Args...) from R (Args...)
// R (C::*)(Args...) const from R (Args...) const
// R (C::*)(Args...) volatile from R (Args...) volatile
// compile error if signature is not a valid member function signature
template <typename, typename>
struct build_class_function;
template <typename C, typename R, typename ... Args>
struct build_class_function<C, R (Args...)>
{ using type = R (C::*)(Args...); };
template <typename C, typename R, typename ... Args>
struct build_class_function<C, R (Args...) const>
{ using type = R (C::*)(Args...) const; };
template <typename C, typename R, typename ... Args>
struct build_class_function<C, R (Args...) volatile>
{ using type = R (C::*)(Args...) volatile; };
check
辅助结构的发现与此结合,我们就得到了针对函数对象的检查元函数:// determine whether a class C has an operator() with signature S
template <typename C, typename S>
struct is_functor_with_signature
{
typedef char (& yes)[1];
typedef char (& no)[2];
// helper struct to determine that C::operator() does indeed have
// the desired signature; &C::operator() is only of type
// R (C::*)(Args...) if this is true
template <typename T, T> struct check;
// T is needed to enable SFINAE
template <typename T> static yes deduce(check<
typename build_class_function<C, S>::type, &T::operator()> *);
// fallback if check helper could not be built
template <typename> static no deduce(...);
static bool constexpr value = sizeof(deduce<C>(0)) == sizeof(yes);
};
第三步:将所有部分组合在一起
我们快要完成了。现在我们只需要决定何时使用自由函数,何时使用类函数对象元函数。幸运的是,C++11提供了一个std::is_class
特性,我们可以用它来做这件事。所以我们只需要针对一个布尔参数进行特化:
// C is a class, delegate to is_functor_with_signature
template <typename C, typename S, bool>
struct is_callable_impl
: std::integral_constant<
bool, is_functor_with_signature<C, S>::value
>
{};
// F is not a class, delegate to is_function_with_signature
template <typename F, typename S>
struct is_callable_impl<F, S, false>
: std::integral_constant<
bool, is_function_with_signature<F, S>::value
>
{};
现在我们可以加入最后一块拼图,也就是我们实际的is_callable
trait:
// Determine whether type Callable is callable with signature Signature.
// Compliant with functors, i.e. classes that declare operator(); and free
// function pointers: R (*)(Args...), but not R (Args...)!
template <typename Callable, typename Signature>
struct is_callable
: is_callable_impl<
Callable, Signature,
std::is_class<Callable>::value
>
{};
is_callable.hpp
可供我们在项目中使用。
完整代码:
namespace // implementation detail
{
// build R (*)(Args...) from R (Args...)
// compile error if signature is not a valid function signature
template <typename, typename>
struct build_free_function;
template <typename F, typename R, typename ... Args>
struct build_free_function<F, R (Args...)>
{ using type = R (*)(Args...); };
// build R (C::*)(Args...) from R (Args...)
// R (C::*)(Args...) const from R (Args...) const
// R (C::*)(Args...) volatile from R (Args...) volatile
// compile error if signature is not a valid member function signature
template <typename, typename>
struct build_class_function;
template <typename C, typename R, typename ... Args>
struct build_class_function<C, R (Args...)>
{ using type = R (C::*)(Args...); };
template <typename C, typename R, typename ... Args>
struct build_class_function<C, R (Args...) const>
{ using type = R (C::*)(Args...) const; };
template <typename C, typename R, typename ... Args>
struct build_class_function<C, R (Args...) volatile>
{ using type = R (C::*)(Args...) volatile; };
// determine whether a class C has an operator() with signature S
template <typename C, typename S>
struct is_functor_with_signature
{
typedef char (& yes)[1];
typedef char (& no)[2];
// helper struct to determine that C::operator() does indeed have
// the desired signature; &C::operator() is only of type
// R (C::*)(Args...) if this is true
template <typename T, T> struct check;
// T is needed to enable SFINAE
template <typename T> static yes deduce(check<
typename build_class_function<C, S>::type, &T::operator()> *);
// fallback if check helper could not be built
template <typename> static no deduce(...);
static bool constexpr value = sizeof(deduce<C>(0)) == sizeof(yes);
};
// determine whether a free function pointer F has signature S
template <typename F, typename S>
struct is_function_with_signature
{
// check whether F and the function pointer of S are of the same
// type
static bool constexpr value = std::is_same<
F, typename build_free_function<F, S>::type
>::value;
};
// C is a class, delegate to is_functor_with_signature
template <typename C, typename S, bool>
struct is_callable_impl
: std::integral_constant<
bool, is_functor_with_signature<C, S>::value
>
{};
// F is not a class, delegate to is_function_with_signature
template <typename F, typename S>
struct is_callable_impl<F, S, false>
: std::integral_constant<
bool, is_function_with_signature<F, S>::value
>
{};
}
// Determine whether type Callable is callable with signature Signature.
// Compliant with functors, i.e. classes that declare operator(); and free
// function pointers: R (*)(Args...), but not R (Args...)!
template <typename Callable, typename Signature>
struct is_callable
: is_callable_impl<
Callable, Signature,
std::is_class<Callable>::value
>
{};
Ideone示例和一些测试
尽管这种方法不适用于重载函数,在其它情况下(自由函数、实现operator()
的类和lambda表达式),这个简短的解决方案可以在C++11中使用:
template <typename T, typename Signature>
struct is_callable: std::is_convertible<T,std::function<Signature>> { };
std::is_invocable
可用。template<typename F>
concept FunctionObject = requires (F) {
&F::operator();
};
operator()
被重载,它将无法工作。 - Waker是否可以创建一个is_functor C++特性类?
是的,可以手动实现functor的验证。
我尝试了is_function::value,但这并不起作用。
Example:
struct Test {
public:
bool operator()(int){
return true;
}
};
void test(int){
}
void example(std::function<void(int)> ex){
cout << "Called" << endl;
};
int main()
{
example(test);
example(&test);
example(Test{});
}
话虽如此,用于验证类是否具有函数调用重载运算符(函数对象)的逻辑与上面的代码类似。
也就是说,如果std::function<void(int)>
接受类Test{}
的实例,则表示该类具有函数对象,否则不具有。
以下是源代码:
//F: Test class
//Args...: The arguments, ex: int or none
template <typename F, typename... Args>
struct is_functor :
is_constructible <
function<void(Args ...)>, F
>
{};
示例用法:
is_functor<Test,int>
—> 返回True
is_functor
—> 返回False
is_constructible
是一个trait类,用于识别T是否是可用指定的Arg参数集构建的可构造类型。
对于这个类,可构造类型是一种可以使用特定一组参数构造的类型。
is_constructible
继承自 integral_constant
,可以是 true_type
或者是 false_type
,具体取决于是否使用参数列表 Args 可以构造出 T。
简而言之,它检查给定类是否具有构造函数,例如:
struct Test2 {
Test(bool, int){}
};
std::is_constructible<Test2, bool, int>
-> 结果为True
std::is_constructible<Test2, float>
-> 结果为False
实现示例:
template <typename, typename, typename ...Args>
struct is_constructible_impl : false_type {};
template <typename T, typename ...Args>
struct is_constructible_impl <
void_t<decltype(T(std::declval<Args>()...))>, T, Args...
> : true_type {};
template <typename T, typename ...Args>
struct is_constructible : is_constructible_impl<void_t<>, T, Args...> {};
在实现is_functor
时,检查了std::function<void(int)>
是否接受Test{}
的实例,这是正确的。
参考资料:
template<typename T, typename Sign>
struct is_functor
{
typedef char yes[1];
typedef char no [2];
template <typename U, U> struct type_check;
template <typename _1> static yes &chk(type_check<Sign, &_1::operator()>*);
template <typename > static no &chk(...);
static bool const value = sizeof(chk<T>(nullptr)) == sizeof(yes);
};
修改自这个答案。
它可以像这样使用...
template<typename T>
typename std::enable_if<is_functor<T, void(T::*)()>::value>::type func()
{
}
typename decltype
是什么?如果operator()
被重载,你的解决方案将无效。 - kennytmoperator()
的对象。 - Maxim Egorushkin
is_function<F::operator()>::value
怎么样? - Fiktikresult_of
特性的一些 SFINAE 技巧可以识别任何可调用类型。我有点惊讶的是,似乎没有任何std::is_callable
特性。 - bames53result_of
这个东西。看起来有很多情况下,即使存在另一个有效的重载方式,GCC 也会在一个重载方式上产生错误。 - user2023370is_function<typename F::operator()>::value
也遭到了拒绝:invalid template argument
。 - user2023370F::operator()
这样的东西。即使参数列表为空,所有成员函数都有一个参数列表。您是否正在寻找F::operator()()
的存在?是的,这是可以检测到的。 - Howard Hinnant