Search code examples
c++c++11bindfunction-pointersboost-bind

bind binded function as argument


I have a class foo with a method bar which takes something callable (function-pointer/ functor). this callable something should be passed to another method doit as an binded element with a third method bar_cb method.

#include <functional>
#include <iostream>

class foo {
public:
    template<typename T>
    void bar(T&& t) {
        std::cout << "bar\n";
        doit(std::bind(&foo::template bar_cb<T>, this, std::forward<T>(t)));
    }

    template<typename T>
    void doit(T&& t) {
        std::cout << "doit\n";
        t();
    }

    template<typename T>
    void bar_cb(T&& t) {
        std::cout << "bar_cb\n";
        t();
    }
};


void lala() {
    std::cout << "lala\n";
}

class functor {
public:
    void operator()() {
        std::cout << "functor::operator()\n";
    }
};


int main() {
    foo f;
    functor fn;
    f.bar(fn);
    f.bar(std::bind(lala));  // error

    return 0;
}

This works fine for functors but not for binded functions as argument for foo::bar (lala in my example). Is it possible to pass an unknowable type to a method and bind it in this method as an argument to another (and if so how)?

I know I could wrap a functor (std::function for example) around the function but since I can call an unknowable type I think there is a way to also bind it (I think I'm just missing something simple).

Here a link to an example.


Solution

  • The primary problem is that your bar_cb(T&&) doesn't deduce the template argument because the template argument is actually specified when using &foo::template bar_cb<X> with some template argument X. The bind() expression will, however, copy the bound function, i.e., it may or may not have the type which would be deduced. Also, std::bind() will not pass bind()-expression through but will rather call them!

    The easiest work around is to not use std::bind() to bind the function but rather to use a lambda function:

    template<typename T>
    void bar(T&& t) {
        std::cout << "bar\n";
        doit([=](){ this->bar_cb(t); });
    }
    

    Doing so let's the compiler deduce the correction argument type for bar_cb() (with C++14 you may want to use the capture [this,t = std::forward<T>(t)] although your bar_cb() still won't see an rvalue).

    To pass an already bind()-expression through another bind()-expression, without having bind() consider the inner bind()-expression a bind()-expression you need to make it look as if it is not a bind()-expression. You could do so with a thin function wrapper:

    template <typename Fun>
    class unbinder {
        Fun fun;
    public:
        template <typename F>
        unbinder(F&& fun): fun(std::forward<F>(fun)) {}
        template <typename... Args>
        auto operator()(Args&&... args) const
            -> decltype(fun(std::forward<Args>(args)...)) {
            return fun(std::forward<Args>(args)...);
        }
    };
    template <typename Fun>
    auto unbind(Fun&& fun)
        -> unbinder<Fun> {
        return unbinder<Fun>(std::forward<Fun>(fun));
    }
    

    Since the function stored in the bind() expression will be passed by lvalue, you'll need a different declaration for your bar_cb(), however:

    template<typename T>
    void bar_cb(T& t) {
        ...
    }
    

    With that, you can register the bind()-expression using

    f.bar(unbind(std::bind(lala)));
    

    If you want to use f.bar(std::bind(lala)) you'll need a conditional definition of bar(): if it receives a bind()-expression it needs to automatically hide the fact that it is a bind()-expression by applying unbind() or something similar:

    template<typename T>
    typename std::enable_if<!std::is_bind_expression<typename std::decay<T>::type>::value>::type
    bar(T&& t) {
        std::cout << "bar (non-bind)\n";
        doit(std::bind(&foo::template bar_cb<T>, this, std::forward<T>(t)));
    }
    template<typename T>
    typename std::enable_if<std::is_bind_expression<typename std::decay<T>::type>::value>::type
    bar(T&& t) {
        std::cout << "bar (bind)\n";
        doit(std::bind(&foo::template bar_cb<unbinder<T>>, this, unbind(std::forward<T>(t))));
    }