是一个 is_functor C++特质类别可能吗?

发布于 2024-12-31 21:50:12 字数 265 浏览 3 评论 0原文

如果参数是 C++ 函数对象(函子),如何静态推断?

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

我尝试了 is_function::value,但这不起作用。似乎也没有 is_functor 特征,所以也许这是不可能的。我似乎只是在寻找特定的成员函数,在本例中是函数调用运算符:F::operator()

How can I deduce statically if an argument is a C++ function object (functor)?

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

I tried is_function<F>::value, but this doesn't work. It also seems there is no is_functor trait, so perhaps it's not possible. I appear to be only looking for a specific member function, in this case the function call operator: F::operator().

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

剩余の解释 2025-01-07 21:50: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 仅当 C 确实具有 void C::operator()() 时才是有效的模板实例化常量。但要做到这一点,我们首先必须将 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 辅助结构的发现放在一起,我们得到函子对象的 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 特征:

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

It is possible to create such a trait, with two restrictions:

  1. For the compiler, a free function is something fundamentally different from a class functor that overloads operator(). Thus we have to treat both cases seperately when implementing. This is not a problem for usage though, we can hide this implementation detail from the user.
  2. We need to know the signature of the function you want to call. This is usually not a problem, and it does have the nice side effect that our trait is able to handle overloads pretty natively.

Step one: Free functions

Let's start with free functions, because they are little easier to detect. Our task is, when given a function pointer, to determine whether the signature of that function pointer matches the signature passed as the second template argument. To be able to compare those, we either need to get a grasp of the underlying function signature, or create a function pointer of our signature. I arbitrarily chose the latter:

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

Now all that's left to do is to compare and we are done with the free function part:

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

Step two: Class functors

This one is a little more involved. We could easily detect with SFINAE whether a class defines an 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);
};

but that does not tell us whether one exists for our desired function signature! Luckily, we can use a trick here: pointers are valid template parameters. Thus we can first use the member function pointer of our desired signature, and check whether &T::operator() is of that type:

template <typename T, T> struct check;

Now check<void (C::*)() const, &C::operator()> will only be a valid template instantiation if C does indeed have a void C::operator()() const. But to do this we first have to combine C and the signature to a member function pointer. As we already have seen, we need to worry about two extra cases we did not have to care about for free functions: const and volatile functions. Besides that it's pretty much the same:

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

Putting that and our findings concerning the check helper struct together, we get our check metafunction for functor objects:

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

Step three: Putting the pieces together

We are almost done. Now we only need to decide when to use our free function, and when the class functor metafunctions. Luckily, C++11 provides us with a std::is_class trait that we can use for this. So all we have to do is specialize on a boolean parameter:

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

So we can finally add the last piece of the puzzle, being our actual 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
      >
{};

Now we clean up our code, put implementation details into anonymous namespaces so they are not acessible outside of our file, and have a nice is_callable.hpp to use in our project.

Full code

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 example with some tests

http://ideone.com/7PWdiv

沉鱼一梦 2025-01-07 21:50:12

虽然这不适用于重载函数,但对于所有其他情况(自由函数、实现 operator() 的类和 lambda),这个简短的解决方案适用于 C++11:

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

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

Although this does not work for overloaded functions, for all other cases (free functions, classes implementing operator(), and lambdas) this short solutions works in C++11:

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

Note: std::is_invocable is available since C++17.

乖乖兔^ω^ 2025-01-07 21:50:12

您可以在c++20中使用以下概念

template<typename F>
concept FunctionObject = requires (F) {
    &F::operator();
};

You can use the following concept in c++20

template<typename F>
concept FunctionObject = requires (F) {
    &F::operator();
};
情感失落者 2025-01-07 21:50:12

is_functor C++ 特征类可能吗?

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

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

  1. 你走在正确的道路上,可以使用 std::function 来实现
  2. 请记住,std::function 在其构造函数中接受函数函数指针函子实例。

    示例:

    结构测试{
    民众:
      布尔运算符()(int){
        返回真;
      }
    };
    
    无效测试(int){
    
    }
    
    void 示例(std::function ex){
      计算<< “被叫”<<结束;
    };
    
    int main()
    {
      示例(测试);
      示例(&测试);
      示例(测试{});
    }
    

也就是说,用于验证类是否具有函数调用重载运算符(函子)的逻辑与上面的代码类似。

也就是说,如果 std::function 接受类的实例 Test{} 则表示该类有函子,否则没有t。

可能的解决方案示例

这是源代码:

//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 ->真实结果

is_functor -> False 结果

有关 std::is_constructible 的信息

是一个特征类,用于标识 T 是否是具有 Arg 指定的参数类型集的可构造类型。

对于此类,可构造类型是可以使用一组特定参数构造的类型。

is_constructible 继承自 integral_constant,为 true_typefalse_type,具体取决于 T 是否可使用参数列表构造参数。

简而言之,它检查给定的类是否有构造函数,例如:

struct Test2 {
   Test(bool, int){}
};

std::is_constructible -> ;真实结果

std::is_constructible ->错误结果

实现示例:

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 是否接受 Test 的实例{},这是真的。

参考文献:

std::is_constructible是如何实现的?实现了吗?

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

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

Is an is_functor C++ trait class possible?

Yes, it is possible to manually implement a validation for functors.

I tried is_function::value, but this doesn't work.

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

That said, the logic that will be used to validate if a class has a function call overload operator (functor) is similar to the code above.

That is, if the std::function<void(int)> accepts an instance of the class Test{} means the class has a functor, otherwise it doesn't.

Example of an possible solution

Here is the source code:

//F: Test class
//Args...: The arguments, ex: int or none
template <typename F, typename... Args>
  struct is_functor :
      is_constructible <
          function<void(Args ...)>, F
      >
  {};

Example usage:

is_functor<Test, int> -> True result

is_functor -> False result

Info about std::is_constructible

Is a trait class that identifies whether T is a constructible type with the set of argument types specified by Arg.

For this class, a constructible type is a type that can be constructed using a particular set of arguments.

is_constructible inherits from integral_constant as being either true_type or false_type, depending on whether T is constructible with the list of arguments Args.

In short, it checks if a given class has a constructor, for example:

struct Test2 {
   Test(bool, int){}
};

std::is_constructible<Test2, bool, int> -> True result

std::is_constructible<Test2, float> -> False result

Example of implementation:

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

Final explanation

In the implementation of is_functor it was checked if std::function<void(int)> accepts an instance of Test{}, which is true.

References:

How is std::is_constructible<T, Args> implemented?

Can std::is_invocable be emulated within C++11?

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

萌无敌 2025-01-07 21:50:12
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()
{
}
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);     
};

Altered from this answer.

It could be used like...

template<typename T>
typename std::enable_if<is_functor<T, void(T::*)()>::value>::type func()
{
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文