How std::bind works with member functions
Asked Answered
W

2

54

I'm working with std::bind but I still don't get how it works when we use it with member class functions.

If we have the following function:

double my_divide (double x, double y) {return x/y;}

I understand perfectly well the next lines of code:

auto fn_half = std::bind (my_divide,_1,2);               // returns x/2

std::cout << fn_half(10) << '\n';                        // 5

But now, with the following code where we have a bind to member function I have some questions.

struct Foo {
    void print_sum(int n1, int n2)
    {
        std::cout << n1+n2 << '\n';
    }
    int data = 10;
};

Foo foo;

auto f = std::bind(&Foo::print_sum, &foo, 95, _1);
f(5);
  • Why is the first argument a reference? I'd like to get a theoretical explanation.

  • The second argument is a reference to the object and it's for me the most complicated part to understand. I think it's because std::bind needs a context, am I right? Is always like this? Has std::bind some sort of implementation to require a reference when the first argument is a member function?

Wingback answered 5/6, 2016 at 0:42 Comment(2)
A & followed by an identifier does not mean "reference". Not unless it's declaring a variable, in which case the & will be preceded by a typename. When & is applied to an identifier, you get a pointer.Charla
In general, avoid using std::bind. It has some really deep quirks that make it produce surprising results even after you master the basics. I find it isn't worth it.Platino
A
100

When you say "the first argument is a reference" you surely meant to say "the first argument is a pointer": the & operator takes the address of an object, yielding a pointer.

Before answering this question, let's briefly step back and look at your first use of std::bind() when you use

std::bind(my_divide, 2, 2)

you provide a function. When a function is passed anywhere it decays into a pointer. The above expression is equivalent to this one, explicitly taking the address

std::bind(&my_divide, 2, 2)

The first argument to std::bind() is an object identifying how to call a function. In the above case it is a pointer to function with type double(*)(double, double). Any other callable object with a suitable function call operator would do, too.

Since member functions are quite common, std::bind() provides support for dealing with pointer to member functions. When you use &print_sum you just get a pointer to a member function, i.e., an entity of type void (Foo::*)(int, int). While function names implicitly decay to pointers to functions, i.e., the & can be omitted, the same is not true for member functions (or data members, for that matter): to get a pointer to a member function it is necessary to use the &.

Note that a pointer to member is specific to a class but it can be used with any object that class. That is, it is independent of any particular object. C++ doesn't have a direct way to get a member function directly bound to an object (I think in C# you can obtain functions directly bound to an object by using an object with an applied member name; however, it is 10+ years since I last programmed a bit of C#).

Internally, std::bind() detects that a pointer to a member function is passed and most likely turns it into a callable objects, e.g., by use std::mem_fn() with its first argument. Since a non-static member function needs an object, the first argument to the resolution callable object is either a reference or a [smart] pointer to an object of the appropriate class.

To use a pointer to member function an object is needed. When using a pointer to member with std::bind() the second argument to std::bind() correspondingly needs to specify when the object is coming from. In your example

std::bind(&Foo::print_sum, &foo, 95, _1)

the resulting callable object uses &foo, i.e., a pointer to foo (of type Foo*) as the object. std::bind() is smart enough to use anything which looks like a pointer, anything convertible to a reference of the appropriate type (like std::reference_wrapper<Foo>), or a [copy] of an object as the object when the first argument is a pointer to member.

I suspect, you have never seen a pointer to member - otherwise it would be quite clear. Here is a simple example:

#include <iostream>

struct Foo {
    int value;
    void f() { std::cout << "f(" << this->value << ")\n"; }
    void g() { std::cout << "g(" << this->value << ")\n"; }
};

void apply(Foo* foo1, Foo* foo2, void (Foo::*fun)()) {
    (foo1->*fun)();  // call fun on the object foo1
    (foo2->*fun)();  // call fun on the object foo2
}

int main() {
    Foo foo1{1};
    Foo foo2{2};

    apply(&foo1, &foo2, &Foo::f);
    apply(&foo1, &foo2, &Foo::g);
}

The function apply() simply gets two pointers to Foo objects and a pointer to a member function. It calls the member function pointed to with each of the objects. This funny ->* operator is applying a pointer to a member to a pointer to an object. There is also a .* operator which applies a pointer to a member to an object (or, as they behave just like objects, a reference to an object). Since a pointer to a member function needs an object, it is necessary to use this operator which asks for an object. Internally, std::bind() arranges the same to happen.

When apply() is called with the two pointers and &Foo::f it behaves exactly the same as if the member f() would be called on the respective objects. Likewise when calling apply() with the two pointers and &Foo::g it behaves exactly the same as if the member g() would be called on the respective objects (the semantic behavior is the same but the compiler is likely to have a much harder time inlining functions and typically fails doing so when pointers to members are involved).

Articulation answered 5/6, 2016 at 1:36 Comment(8)
Half way reading this, I was puzzled, who might have wrote such a great explanation? I could've guessed!Outgrowth
Amazing answer, Dietmar. For me, the keys are: (1) While function names implicitly decay to pointers to functions, the same is not true for member functions: to get a pointer to a member function it is necessary to use the &., (2) std::bind() detects that a pointer to a member function is passed and most likely turns it into a callable object, and (3) To use a pointer to member function an object is needed. When using a pointer to member with std::bind() the second argument to std::bind() correspondingly needs to specify when the object is coming from. Thank you very much!Wingback
How would I go about to use this as a template function? Given the following: template <typename C> using TimeOutHandler2 = void (C::*)(QNetworkReply*); How should I write a member function in another class which takes an object of type C and a member function with the signature of TimeOutHandler2 and store the resulting pointer? This clearly doesn't work. template <typename C> void setTimeOutHandler(C obj, TimeOutHandler2 handler) { mTimeOutHandler = std::bind(handler, obj); } Sorry, can't get the markup to work...Ballew
@marcbf: you didn't say what type mTimeOutHandler has. I assume it is something akin to std::function<void(QNetworkReply*)>. If so, you'd assign mTimeOutHandler = std::bind(handler, obj, std::placeholders::_1); (note that I would expect obj to be passed by pointer or reference to your function; in the former case you can just pass it on, in the latter case you'd probably want to pass std::ref(obj); assuming it is actually copyable, pass by value would work but it seems unusual). Using bind() in recent versions of C++ is unusual: I'm normally passing a lambda.Bannerol
@DietmarKühl, thanks a lot for replying. What I'm trying to do is to write a pendent to this, just for member functions instead. using TimeOutHandler = void (*)(QNetworkReply*); void setTimeOutHandler(TimeOutHandler handler) { mTimeOutHandler = handler; } TimeOutHandler mTimeOutHandler = nullptr; I'm just not very knowledgable when it comes to templates. In my previous comment I actually meant to assign to mTimeOutHandler2 defined as TimeOutHandler2. The idea was to then check if either mTimeOutHandler or mTimeOutHandler2 isn`t a nullptr and then call it.Ballew
@marcbf: what is the type of mTimeOutHandler2?Bannerol
@DietmarKühl it's this: template <typename C> using TimeOutHandler2 = void (C::*)(QNetworkReply*) Which apparently is wrong. At least I can't get it to work.Ballew
Well, I finally seem to have cracked it! :-) This seems to do the trick. using TimeOutHandler = void (*)(QNetworkReply*); template <typename T> using TimeOutHandlerMF = void (T::*)(QNetworkReply*); void setTimeOutHandler(TimeOutHandler handler) { mTimeOutHandler = handler; } template <typename T> void setTimeOutHandler(T* obj, TimeOutHandlerMF<T> handler) { mTimeOutHandler = std::bind(handler, obj, std::placeholders::_1); } std::function<void(QNetworkReply*)> mTimeOutHandler;Ballew
H
7

From std::bind docs:

bind( F&& f, Args&&... args ); where f is a Callable, in your case that is a pointer to member function. This kind of pointers has some special syntax compared to pointers to usual functions:

typedef  void (Foo::*FooMemberPtr)(int, int);

// obtain the pointer to a member function
FooMemberPtr a = &Foo::print_sum; //instead of just a = my_divide

// use it
(foo.*a)(1, 2) //instead of a(1, 2)

std::bind(and std::invoke in general) covers all these cases in a uniform way. If f is a pointer-to-member of Foo, then the first Arg provided to bind is expected to be an instance of Foo (bind(&Foo::print_sum, foo, ...) also works, but foo is copied) or a pointer to Foo, like in example you had.

Here is some more reading about pointers to members, and 1 and 2 gives full information about what bind expects and how it invokes stored function.

You also can use lambdas instead std::bind, which could be more clear:

auto f = [&](int n) { return foo.print_sum(95, n); }
Herstein answered 5/6, 2016 at 1:35 Comment(4)
Oddly enough, &Foo::print_sum is not a callable! A pointer to member, even a pointer to member function , does not have a function call operator! std::mem_fn(&Foo::print_sum) would be a callable. However, std::bind() does understand being called with pointer to members as its first argument.Bannerol
@DietmarKühl Callable means a little different thing in c++17: en.cppreference.com/w/cpp/types/is_callable. And the whole family of std::mem_fn, std::bind and std::invoke just provide a nice interface over base Callable types.Herstein
But this is just a question about terminology.Herstein
Thanks @DmitryPanteleev! I was reading also a very good answer here related with function pointers and pointers to member functions.Wingback

© 2022 - 2024 — McMap. All rights reserved.