有没有办法创建由“std::function<>”包装的函数的哈希值?

Is there a way to create a hash of a function wrapped by `std::function<>`?

提问人:StoneThrow 提问时间:4/27/2023 最后编辑:Gabriel StaplesStoneThrow 更新时间:4/27/2023 访问量:188

问:

我有一个 C++ 函数,它接受 a 作为输入参数。
具体来说,一个 .
std::functionstd::function<void (const Message&, Error)>

在我的用例中,调用者可以将 绑定到自由函数或成员函数。std::function

(我对 or 没有经验,所以我发现值得注意的是,相同的对象类型 ,可以绑定到自由函数和成员函数——后者通过使用 .我发现它很有趣,因为它似乎抽象出了函数指针和成员函数指针之间的区别(至少它给了我这种印象))std::bindstd::functionstd::function<void (const Message&, Error)>std::bind

对于我的调试需求,记录与输入参数关联的哈希值(唯一值)会很有用。
在这里,我很快意识到我无法逃避自由函数指针和成员函数指针之间的根本区别。
我可以使用 获取底层免费函数指针,它作为唯一哈希来满足我的需求。
但是,如果绑定到成员函数,则不起作用。
在我的脑海中,我推断,如果绑定到成员函数,那么将指针返回到成员函数指针 - 但事实似乎并非如此。
std::functionvoid (*)(const Message&, Error)std::function::target<void (*)(const Message&, Error)>()std::function<void (const Message&, Error)>std::function<void (const Message&, Error)>class Foostd::function::target<void (Foo::*)(const Message&, Error)>()

这就引出了我的问题:有没有办法从 std::function 实例中获取唯一的哈希值,无论它是绑定到自由函数还是成员函数?

#include <functional>
#include <iostream>

using namespace std;

struct Message {
  int i_;
};

struct Error {
  char c_;
};

class Foo {
public:
  void print(const Message& m, Error e) {
    cout << "member func: " << m.i_ << " " << e.c_ << endl;
  }
};

void print(const Message& m, Error e) {
  cout << "free func: " << m.i_ << " " << e.c_ << endl;
};

void doWork(function<void (const Message&, Error)> f) {
  // I can invoke f regardless of whether it's been bound to a free function or
  // a member function...
  {
    Message m{42};
    Error e{'x'};

    f(m, e);
  }

  // ...but since I don't know whether f is bound to a free function or a member
  // function, I can't use std::function::target<>() to generically get a
  // function pointer, whose (void*) value would have served my need for a
  // hash...
  {
    typedef void (*Fptr)(const Message&, Error);
    typedef void (Foo::*Mfptr)(const Message&, Error);

    Fptr* fptr = f.target<Fptr>();
    Mfptr* mfptr = nullptr;

    cout << "free func target: " << (void*)fptr << endl;

    if (fptr) {
      cout << "free func hash: " << (void*)*fptr << endl;
    }
    else {
      // ...moreover, when f is bound to a Foo member function (using
      // std::bind), std::function::target<>() doesn't return a Foo member
      // function pointer either...I can't reason why not.
      // (this also isn't scalable because in future, f may be bound to a 
      // class Bar or class Baz member function)
      mfptr = f.target<Mfptr>();
      cout << "not a free function; checking for a Foo member function" << endl;
      cout << "member func target: " << (void*)mfptr << endl;

      if (mfptr) {
        cout << "member func hash: " << (void*)*mfptr << endl;
      }
    }
  }
}

int main()
{
  {
    function<void (const Message&, Error)> f = print;

    doWork(f);
  }

  cout << "---" << endl;

  {
    Foo foo;
    function<void (const Message&, Error)> f = bind(&Foo::print,
                                                    &foo,
                                                    placeholders::_1,
                                                    placeholders::_2);

    doWork(f);
  }

  return 0;
}

编译和输出:

$ g++ --version && g++ -g ./main.cpp && ./a.out
g++ (Debian 8.3.0-6) 8.3.0
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

free func: 42 x
free func target: 0x7ffda4547bf0
free func hash: 0x55db499c51e5
---
member func: 42 x
free func target: 0
not a free function; checking for a Foo member function
member func target: 0
c++ 哈希 std-function 成员函数 stdbind

评论

1赞 KamilCuk 4/27/2023
怎么样.... ?std::function::target_type().name()
0赞 joergbrech 4/27/2023
可能与此相关:stackoverflow.com/questions/73744246/......
0赞 Gabriel Staples 4/27/2023
每个函数都只是指向内存中地址的指针。你不能只使用那个地址吗?前任:。如果两个函数相等,则它们具有相同的地址。size_t addr = (size_t)my_func;
0赞 StoneThrow 4/27/2023
@KamilCuk -- 看起来很有希望;我会对此进行更多尝试,如果您倾向于将您的评论扩展为答案,我很乐意投赞成票/接受。
1赞 StoneThrow 4/27/2023
@GabrielStaples——这正是我想到的,并试图去做。问题在于获取该地址。不允许我更改 的签名,因此我只能使用提供的任何内容。doWork()std::function

答:

3赞 KamilCuk 4/27/2023 #1

代码如下:

#include <functional>
#include <iostream>
#include <vector>
#include <string>
#include <cstdint>

int f(int a) { return -a; }
int f2(int a) { return a; }

int main() {
    std::vector<std::function<int(int)>> fn{
        f,
        f,
        f2,
        f2,
        [](int a) {return -a;},
        [](int a) {return -a;},
        [](int a) {return -a;},
    };

    for (auto&& a : fn) {
        const auto t = a.target<int(*)(int)>();
        const auto hash = t ?
            (size_t)(uintptr_t)(void*)*t :
            a.target_type().hash_code();
        std::cout << hash << '\n';
    }
}

两个 f 函数、两个 f2 函数和 3 个 lambda 函数的初始化向量。因此,我们期待两个相同的哈希值,两个相同的哈希值,并且每个 lambda 都是一个新类型 - 3 个不同的哈希值。代码输出:

4198918
4198918
4198932
4198932
11513669940284151167
7180698749978361212
13008242069459866308

评论

3赞 R2RT 4/27/2023
如果 lambda/functor 来自某个工厂,则会产生相同的哈希值,例如,因为我们拥有并共享类型。auto factory(int x){ return [x](int y){ return x + y;}; }factory(10)factory(20)
2赞 R2RT 4/27/2023
这对 OP 来说可能无关紧要,也不会使答案无效,但我认为它应该作为脚注说出来。
2赞 Gabriel Staples 4/27/2023
对 s(名称损坏的 lambda 函数名称)进行哈希处理,而不仅仅是直接通过获取hash_code,这有什么好处吗?target_type().name()std::hash<std::string_view>{}(a.target_type().name())target_type().hash_code()
0赞 Gabriel Staples 4/28/2023
为什么要将函数地址、 、 转换为,然后转换为 via,而不是直接转换为 via?我总是直接投射指针,中间没有中间投射。*tvoid*uintptr_tsize_t(size_t)(uintptr_t)(void*)*tsize_t(size_t)*tsize_t
1赞 KamilCuk 4/28/2023
但是,我同意,这一切都没有意义。因为转换本身是实现定义的,我们不妨期望编译器只以实现定义的方式提供支持,而不是执行标准的 C++ 恶作剧以避免标准的 C++ 未定义行为。我们都知道只有 3 个 C++ 编译器很重要。但从另一方面来看,你永远不知道编译器的优化器部分的编写者何时会介入,确定这段代码是未定义的行为,并对其进行全面优化。所以它就是这样。(size_t)*t