一个is_functor的C++特性类是否可能?

13

我该如何在静态情况下推断一个参数是否为C++函数对象(仿函数)?

template <typename F>
void test(F f) {}

我尝试了is_function<F>::value,但它并没有起作用。似乎也没有is_functor特性,所以可能不可能实现。在这种情况下,我只需要查找一个特定的成员函数,即函数调用运算符:F::operator()


is_function<F::operator()>::value 怎么样? - Fiktik
2
这个网址 http://groups.google.com/group/comp.lang.c++.moderated/msg/e5fbc9305539f699 可能会对你有所帮助。 - pmr
1
你是想测试函数对象还是任何可调用对象?似乎使用 result_of 特性的一些 SFINAE 技巧可以识别任何可调用类型。我有点惊讶的是,似乎没有任何 std::is_callable 特性。 - bames53
@bames53:我整天都在研究 result_of 这个东西。看起来有很多情况下,即使存在另一个有效的重载方式,GCC 也会在一个重载方式上产生错误。 - user2023370
@Fitkit:这个代码无法通过编译,而且is_function<typename F::operator()>::value也遭到了拒绝:invalid template argument - user2023370
不存在 F::operator() 这样的东西。即使参数列表为空,所有成员函数都有一个参数列表。您是否正在寻找 F::operator()() 的存在?是的,这是可以检测到的。 - Howard Hinnant
5个回答

12
可以创建这样的特性,但有两个限制:
  1. 对于编译器而言,自由函数与类函子重载operator()时是根本不同的事情。因此,在实现时需要分别处理这两种情况。但这对于使用者来说并不是问题,我们可以将这个实现细节隐藏起来。
  2. 我们需要知道要调用的函数的签名。通常这不是问题,而且它具有一个好处,就是我们的特性能够非常自然地处理重载。
步骤一:自由函数 让我们从自由函数开始,因为它们比较容易检测。我们的任务是,当给定一个函数指针时,确定该函数指针的签名是否与作为第二个模板参数传递的签名相匹配。为了能够比较这些签名,我们需要掌握底层函数签名,或者创建一个我们签名的函数指针。我随意选择了后者。
// 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 和签名组合成一个成员函数指针。正如我们已经看到的,我们需要关注两个额外的情况,这些情况对于自由函数来说我们不必担心: constvolatile 函数。除此之外,它基本上是相同的。
// 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示例和一些测试

http://ideone.com/7PWdiv


2

尽管这种方法不适用于重载函数,在其它情况下(自由函数、实现operator()的类和lambda表达式),这个简短的解决方案可以在C++11中使用:

template <typename T, typename Signature>
struct is_callable: std::is_convertible<T,std::function<Signature>> { };

注意:自C++17起,std::is_invocable可用。

1
您可以在C++20中使用以下概念。
template<typename F>
concept FunctionObject = requires (F) {
    &F::operator();
};

如果operator()被重载,它将无法工作。 - Waker

1

是否可以创建一个is_functor C++特性类?

是的,可以手动实现functor的验证。

我尝试了is_function::value,但这并不起作用。

  1. You are on the right path, it is possible to implement using std::function

  2. Remember that std::function also accepts functions, function pointers and functor instances in its constructor.

    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


关于 std::is_constructible 的信息

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{}的实例,这是正确的。

参考资料:

如何实现std::is_constructible<T, Args>?

能否在C++11中模拟std::is_invocable?

https://replit.com/@LUCASP6/RowdyAlphanumericCode#main.cpp


0
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()被重载,你的解决方案将无效。 - kennytm
你对函数对象的定义不完整。标准的函数对象可以是一个函数指针或者是一个重载了operator()的对象。 - Maxim Egorushkin
我发布了一个不同的解决方案; @MaximYegorushkin 但新的解决方案在这方面没有改变,嗯 - David

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接