c++hashstd-functionmember-functionsstdbind

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


I have a C++ function that takes a std::function as an input argument.
Specifically, a std::function<void (const Message&, Error)>.

In my use-case, the caller may bind the std::function to either a free function or a member function.

(I'm not experienced with std::bind or std::function, so I found it noteworthy that the same object type, std::function<void (const Message&, Error)>, can be bound to a free function as well as a member function -- the latter by using std::bind. I found it interesting because it seemed to abstract away the difference between a function pointer and a member function pointer (at least it gave me that impression))

For my debugging need, it would be useful to log a hash, something unique, associated with the std::function input argument.
Here's where I quickly realized I can't escape that fundamental difference between free function pointers and member function pointers.
I can get the underlying void (*)(const Message&, Error) free function pointer using std::function::target<void (*)(const Message&, Error)>(), which serves my needs as a unique hash.
But that doesn't work if the std::function<void (const Message&, Error)> is bound to a member function.
In my head, I reasoned that if the std::function<void (const Message&, Error)> was bound to a class Foo member function, then std::function::target<void (Foo::*)(const Message&, Error)>() would return the pointer to a member function pointer -- but that didn't seem to be the case.

Which leads to my question: is there any way to generically get a unique hash from a std::function instance regardless whether it's bound to a free function or a member 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;
}

Compilation and output:

$ 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

Solution

  • The following code:

    #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';
        }
    }
    

    Initialized vector of two f functions, two f2 functions, and 3 lambda functions. Thus we are expecting two same hashes, two same hashes, and each lambda is a new type - 3 different hashes. The code outputs:

    4198918
    4198918
    4198932
    4198932
    11513669940284151167
    7180698749978361212
    13008242069459866308