Search code examples
c++c++11functorstd-functionstdbind

Is it possible to access the arguments from an std::bind object?


Please consider the following scenario.

A hypothetical pop-up menu class that displays some actions, and when one of them is selected it will call the passed in action which is in form of an std::function:

PopupMenu::PopupMenu(std::function<void(RowItem*)> editRowFunction, RowItem *item)
    : _editRowFunction(editRowFunction)
    , _item(item) {
}

Then at some point it might call execute:

PopupMenu::execute(} {
    _editRowFunction(_item);
}

Then I have this other class that's a UI object:

class EditorWidget {
    void editRow(RowItem *row) {
        //edit the row
    }
}

And here's how I am using all of them:

int main() {
    auto item = new RowItem();
    auto editorWidget = new EditorWidget();
    PopupMenu menu(std::bind(&EditorWidget::editRow, editorWidget, item), item);
    menu.execute();
    return 0;
}

Everything works. My question is the following: If I am already passing the argument item in std::bind, why do I have to pass it again as a second parameter in order to be able to call the bound function with that argument? If I don't, and try to call the function just by itself from PopupMenu::execute(), I get a compiler error.

The other way around it is to make the constructor of PopupMenu like this:

PopupMenu::PopupMenu(std::function<void()> editRowFunction)
    : _editRowFunction(editRowFunction) {}

And if I do it that way then I call it this way:

PopupMenu::execute() {
_editRowFunction();
}

What I don't like about this method is that I can pretty much pass any bound function in the PopupMenu constructor and it will be called. But that's not what I want, I want to enforce only a function with a specific signature.

I can also pass a lambda, yes. But let's try to solve it without lambdas. Thank you all in advance for your help.


Solution

  • std::bind(&EditorWidget::editRow, editorWidget, item)
    

    std::bind here is creating a functional object that takes a pointer to a member function EditorWidget::editRow, bound to an object editorWidget, using the parameter item. What you've done is actually fix the parameter to the function EditorWidget::editRow with the parameter item. So effectively you've created a function object that takes no argument (since you've fixed it), and returns void.

    There's actually no need for the constructor of PopupMenu to have a second parameter of type RowItem*. You could change the constructor like so:

    PopupMenu::PopupMenu(std::function<void()> editRowFunction)
        : _editRowFunction(editRowFunction)
    {
    }
    

    and then call your function object like this:

    PopupMenu::execute(} {
        _editRowFunction();
    }
    

    In your current code the parameter _item is not being used by the function object you pass into the constructor PopupMenu. It satisfies the compiler since _editRowFunction is of type std::function<void(RowItem*)>.

    Here's a simple example to illustrate to the point:

    #include <iostream>
    #include <functional>
    
    struct callable
    {
        callable(std::function<void(std::string)> fn) : mFn(fn)
        {}
    
        std::function<void(std::string)> mFn;
    
        void Run() { mFn("world"); }
    };
    
    struct Foo {
        void print(std::string msg)
        {
            std::cout << msg << '\n';
        }
    };
    
    int main()
    {
        Foo f;
        auto fn = std::bind(&Foo::print, &f, "hello");
        fn();
    
        callable c(fn);
        c.Run(); //expecting "world" to be printed
    }
    

    You might expect the output to be:

    hello
    world
    

    but actually it's:

    hello
    hello
    

    Live demo.

    What I could do is change the definition of the function object like this:

     auto fn = std::bind(&Foo::print, &f, std::placeholders::_1); //uses a placeholder
    

    and I get the expected output. You could do something similar without having to make many changes to your current implementation.