Reputation: 5334
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
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