【问题标题】:Can two functors be compared for equality?可以比较两个函子是否相等吗?
【发布时间】:2019-04-15 15:01:21
【问题描述】:

有没有办法让接收两个函子作为参数的方法找出它们是否指向同一个函数?具体来说,有一个这样的结构:

struct FSMAction {
    void action1() const { std::cout << "Action1 called." << std::endl; }
    void action2() const { std::cout << "Action2 called." << std::endl; }
    void action3() const { std::cout << "Action3 called." << std::endl; }

private:
    // Maybe some object-specific stuff.
};

还有这样的方法:

bool actionsEqual(
    const std::function<void(const FSMAction&)>& action1, 
    const std::function<void(const FSMAction&)>& action2)
{
    // Some code.
}

是否有“一些代码”将返回 true 仅用于:

actionsEqual(&FSMAction::action1, &FSMAction::action1)

但不适用于:

actionsEqual(&FSMAction::action1, &FSMAction::action2)

也许这个问题没有任何意义(第一个线索是互联网上似乎没有关于它的任何内容......)。如果是这样,您能否给出提示,为什么以及是否有办法完成“类似”的事情? (基本上,我希望有一组只有上述意义上的“独特”项目的回调。)

【问题讨论】:

  • 这听起来像是一个 XY 问题。您要解决什么问题才能确保使用相同的功能?
  • 你能用成员指针代替std::function吗?
  • 这个问题可能会更清楚一点。在标题中您要求比较函子,但问题似乎不是比较两个Actions,而是找出两个std::functions 是否引用Action 的相同方法。您打算如何使用Actions?如果它是一个带有operator() 的“普通函子”,您可能只需提供一个Action::operator== 并完成它......
  • 请注意,判断两个指针是否指向 same 函数与两个指针是否指向执行相同操作的不同函数之间存在差异。前者只是指针比较;后者会更困难。
  • @NathanOliver 也许这是一个 XY 问题......我想允许我的班级的用户(基本上是一个有限状态机)为每个状态分配一个回调,以便在何时触发进入那个状态。如果两个状态的回调相等,我希望它被识别为相同。

标签: c++ c++11 functor


【解决方案1】:

原始函数最终是一个指针。你可以dig it outstd::functionstd::function::target,然后它只是void* 的比较。

【讨论】:

  • 这并不容易。可能是virtual 函数,不能用一个指针来描述。
  • 不,他们不是。成员函数很特别。您不能将成员函数指针强制转换为原始函数指针。
  • @NathanOliver target() 似乎也适用于成员指针:coliru.stacked-crooked.com/a/76e4e6a1e58c8d97 显然它返回一个指向存储的类型擦除对象的指针。
  • @HolyBlackCat 哦,酷。错过了它是一个函数模板,因此您可以指定所需的指针。我虽然它使用的是函数类型
  • @HolyBlackCat 这个怎么样? Changed Demo of Cat ;-)(我不确定这是赞成还是反对。)
【解决方案2】:

按照 Michael Chourdakis 的回答中的建议直接使用 std::function::target&lt;T&gt;() 是有问题的,因为要使用它,您必须知道存储在 std::function 中的实际类型:

返回值

如果为target_type() == typeid(T),则为指向存储函数的指针,否则为空指针。

例如通过使用T = void (A::*)() const,您将自己限制为仅使用class FSMActionvoid() const 成员函数。此时std::function 开始不比普通的成员函数指针好。


我建议为 std::function 编写一个包装器,使用类型擦除实现 == / !=。这是一个最小的实现:

#include <functional>
#include <iostream>
#include <utility>

template <typename T>
class FancyFunction;

template <typename ReturnType, typename ...ParamTypes>
class FancyFunction<ReturnType(ParamTypes...)>
{
    using func_t = std::function<ReturnType(ParamTypes...)>;
    func_t func;
    bool (*eq)(const func_t &, const func_t &) = 0;

  public:
    FancyFunction(decltype(nullptr) = nullptr) {}

    template <typename T>
    FancyFunction(T &&obj)
    {
        func = std::forward<T>(obj);    
        eq = [](const func_t &a, const func_t &b)
        {
            return *a.template target<T>() ==
                   *b.template target<T>();
        };
    }

    explicit operator bool() const
    {
        return bool(func);
    }

    ReturnType operator()(ParamTypes ... params) const
    {
        return func(std::forward<ParamTypes>(params)...);
    }

    bool operator==(const FancyFunction &other) const
    {
        if (func.target_type() != other.func.target_type())
            return 0;
            
        if (!eq)
            return 1;
        
        return eq(func, other.func);
    }
    
    bool operator!=(const FancyFunction &other) const
    {
        return !operator==(other);
    }
};


struct A
{
    void foo() {}
    void bar() {}
};

int main()
{
    FancyFunction<void(A &)> f1(&A::foo), f2(&A::foo), f3(&A::bar);
    std::cout << (f1 == f2) << '\n';
    std::cout << (f1 == f3) << '\n';
}

Try it live

【讨论】:

  • 我喜欢您的解决方案,我一定会牢记在心!不过,我仍然接受了 Michael Chourdakis 的回答,因为它更轻量级并且可以很好地解决我的具体问题。无论如何,阅读本文的人可以自行决定是否需要您更通用的方法...
猜你喜欢
  • 1970-01-01
  • 2013-08-18
  • 2021-09-14
  • 2023-03-11
  • 2010-12-05
  • 2016-03-06
  • 1970-01-01
  • 2014-09-20
相关资源
最近更新 更多