markzzz
markzzz

Reputation: 47945

Can you help me to understand why this bind works?

I'm trying to understand bind and pre-fill functions in C++.

Here's my example:

#include <iostream>
#include <functional>
#include <vector>

class Voice
{
public:
    double mValue;

private:
};

class VoiceManager
{
public:
    VoiceManager() { }
    ~VoiceManager() { }

    typedef std::function<void(Voice &)> VoiceChangerFunction;
    inline void UpdateVoices(VoiceChangerFunction callback) {
        for (int i = 0; i < mNumOfVoices; i++) {
            callback(mVoices[i]);
        }
    }
    static void SetValue(Voice &voice, unsigned int value) {
        voice.mValue = value;
        std::cout << voice.mValue << std::endl;
    }

private:
    static const int mNumOfVoices = 4;    
    Voice mVoices[mNumOfVoices];
};

int main()
{
    VoiceManager voiceManager;
    VoiceManager::VoiceChangerFunction callback;
    callback = std::bind(&VoiceManager::SetValue, std::placeholders::_1, 100);
    voiceManager.UpdateVoices(callback);
}

Basically, I create a VoiceChangerFunction function (object) that takes a Voice & as first parameter and returns void.

Later, I bind a function that will take as first parameter the one I'll give to it when I call it, and another parameter that I give when I bind it (100, in my example).

Right?

What I don't understand is: then, this function is passed to UpdateVoices(), which take as input a function/object that has 1 param (Voice &), not 2 as created in my bind function (Voice &, unsigned int).

How can it works?

Its like to have void VoiceChangerFunction(Voice &voice) and call VoiceChangerFunction(Voice &voice, unsigned int value ).

The function prototype is different. I mean: the callback bind I created isn't a VoiceChangerFunctions function, because it takes more parameters.

How can it works/match?

Upvotes: 3

Views: 127

Answers (3)

mkaes
mkaes

Reputation: 14119

Your assumption about bind is wrong.
Your bind call returns a function object that will accept one parameter, namely the placeholder. The other parameter on your function is already bound to 100.

A little example:

void foo(int i1, int i2) {};
std::function<void(int,int)> fn1 = std::bind(foo, std::placeholders::_1, std::placeholders::_2);
std::function<void(int)> fn1 = std::bind(foo, std::placeholders::_1, 1);
std::function<void()> fn1 = std::bind(foo, 1, 1);

The bind will create a matching function depending on bound and unbound parameters.

Update

The compiler will generate a struct from the bind expression and a copy of your parameter. Simplified something like this(this will not compile):

struct Function_{
    void(*fn)(Voice &, unsigned int)
    unsigned int i_;

    Function_(void(*f)(Voice &, unsigned int), unsigned int i):fn(f),i_(i){}
    void operator()(Voice& v){
        fn(v, i_);
    }
}

fn is the first parameter which is a function pointer and the bound (100) is the second. Then all you need is some type erasure and your own bind is ready to go.

Upvotes: 2

SergeyA
SergeyA

Reputation: 62553

That is exactly the beauty of bind and std::function at works. You are defining the callback as function taking one argument, and bind is returning a function object which takes one argument.

The main point here is that it actually calls the function which takes 2 parameters, but the second one is fixed, and will always be 100 (in your case). This is the sole purpose of binders - to provide a way to call functions with different set of arguments with some fixed values. If you would be calling the function taking the same set of arguments, there would be no reason to use binders at all!

Knowing that bind is similar to lambdas, the same code could be written as - and probably be more clear:

VoiceManager::VoiceChangerFunction callback;
callback = [](Voice& v) { VoiceManager::SetValue(v, 100); };
voiceManager.UpdateVoices(callback);

And if you are curious how it works, you might try to create a binder framework yourself. If you are only doing it for educational purposes and not worried about too many details, it is not that hard.

Upvotes: 4

TankorSmash
TankorSmash

Reputation: 12747

When you bind, you're making a new function that only takes Voice as a param, that's why it works.

void a_func(int x) { return; }
std::function<void(void)> new_func = std::bind(&a_func, 1);

new_func now has the signature of void(void), so you could pass it around to anywhere that expects a function of type void(void).

When you call new_func, it really calls a_func(1).

Upvotes: 2

Related Questions