user1810087
user1810087

Reputation: 5334

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.

Upvotes: 0

Views: 565

Answers (1)

Dietmar K&#252;hl
Dietmar K&#252;hl

Reputation: 153840

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))));
}

Upvotes: 2

Related Questions