1

I have the following scenario quite often in my c++17 code.

Generic example:

class Receiver{
public:
    typedef std::function<void(const std::vector<uint8_t>& payload)> PROCESS_PACKET;
    PROCESS_PACKET m_callback;
    void run(){
        while(){
            // do stuff, then call callback:
            m_callback(data);
        }
    }
};

class UsesReceiver{
public:
    UsesReceiver(){
        receiver.m_callback=std::bind(&UsesReceiver::processData, this, std::placeholders::_1);
        receiver.run();
    }
private:
    void processData(const std::vector<uint8_t>& payload){
        // here do something implementation specific
    }
    Receiver receiver;
};

What always bugs me is this part:

receiver.m_callback=std::bind(&UsesReceiver::processData, this, std::placeholders::_1);

For this example it is quite simple, since the function only takes one parameter, but as soon as you have more parameters this part is a lot of boilerplate in my opinion.

Because I am thinking: If there is a member function whoese definition is exactly the same as the declared function pointer (e.g. arguments and function type match) there should be a simple one-liner that checks these preconditions at compile time and looks basically like this:

receiver.m_callback=std::bind_quick(&UsesReceiver::processData,this);

Where std::bind_quick, is "just like std::bind" but without placeholders if both function declarations match (checked at compile time).

Since the first couple of comments were "use lambdas instead": This doesn't really solve the problem, because with lambdas you still have to declare "placeholders" for your function arguments:

receiver.m_callback=[this](auto && PH1) { 
            processData(std::forward<decltype(PH1)>(PH1)); 
        };
Constantin Geier
  • 303
  • 4
  • 13
  • 5
    Don't use `bind`, use a lambda instead. Then you can use a technique like this: https://stackoverflow.com/a/65811783/4342498 – NathanOliver Mar 11 '21 at 18:54
  • 2
    Of course, there is. Just use lambdas instead. – Scheff's Cat Mar 11 '21 at 18:54
  • Without declaring an extra macro (which is something I'd like to avoid) using a lambda creates just as much boilerplate for functions with "a lot of arguments": receiver.m_callback=[this](auto && PH1) { processData(std::forward(PH1)); }; – Constantin Geier Mar 11 '21 at 18:59
  • 1
    Why do you want to avoid a macro? Removing/Reducing boiler plate is one of the actual use cases for macros. Why not use the tool that does the job? – NathanOliver Mar 11 '21 at 19:03
  • 3
    You also don't need to be explicit about the placeholders when manually writing a lambda. You can use `[this](auto...&& args) { processData(std::forward(args)...); }` – NathanOliver Mar 11 '21 at 19:05
  • Because I'd expect there to be a std implementation for that. – Constantin Geier Mar 11 '21 at 19:06
  • 4
    There is a std implementation for that in C++20: `std::bind_front`. – StoryTeller - Unslander Monica Mar 11 '21 at 19:09
  • 1
    @ConstantinGeier c++ is not flooded with small utilities for convenience. There's a lot of different "default behaviours" one could want from an automatic member-function wrapper and writing one yourself is just a few lines of code. – super Mar 11 '21 at 19:10
  • @NathanOliver, it turns out my question is useful. Still nobody but one upvoted me :( – Enlico Mar 15 '21 at 14:31

3 Answers3

4

As pointed out in the comments you should generally prefer lambdas over std::bind, even if it doesn't solve your issue.

It's fairly easy to create your quick_bind function using lambdas though.

template <typename Func, typename Obj>
auto quick_bind(Func f, Obj* obj) {
    return [=](auto&&... args) {
        return (obj->*f)(std::forward<decltype(args)>(args)...);
    };
}

Then use it like so

receiver.m_callback=quick_bind(&UsesReceiver::processData, this);

It returns a lambda with a templated operator, so if your assigning it to a std::function it will work as long as there's not a mismatch in function signature.

super
  • 12,335
  • 2
  • 19
  • 29
2

I am of the position that you should have stopped using std::bind in .

Lambdas solve 99.9% of std::bind problems cleaner and in a way more likely to be understood, and you shouldn't even write the remaining 0.1% of std::bind code (which involves passing std::binds to std::bind and the madness that results).

In the raw we get:

receiver.m_callback=[this](auto&payload){processData(payload);};

this is already simpler and clearer in my opinion. Just language primitives, no library functions involved.

If you want a helper function

In , use std::bind_front or in :

template<class F, class...Args>
auto bind_front( F&& f, Args&&...args ) {
  return [f = std::forward<F>(f), tup=std::make_tuple(std::forward<Args>(args)...)](auto&&... more_args)
         ->decltype(auto)
  {
    return std::apply([&](auto&&...args)->decltype(auto){
      return std::invoke( f, decltype(args)(args)..., decltype(more_args)(more_args)... );
    }, tup);
  };
}

a bit of a mess, but gives you

receiver.m_callback=notstd::bind_front(&UsesReceiver::processData, this);

Live example.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
0

Since I also got a really good solution in one of the comments, I am going to sum it up here:

If you are lucky enough to work with c++20, you can use std::bind_front as suggested by @StoryTeller. For the example above, that would look like this:

receiver.m_callback=std::bind_front(&UsesReceiver::processData, this);

If you do not have c++20 support, you can also use absl::bind_front. Excerpt:

The simpler syntax of absl::bind_front() allows you to avoid known misuses with std::bind()

Else,you can also write your own macro or function, as suggested in the answer above or in the comment by @NathanOliver.

Constantin Geier
  • 303
  • 4
  • 13