|
-
September 5th, 2010, 09:21 AM
#1
some problem with boost::bind.
The sample code is like this:
#include <boost/bind.hpp>
#include <boost/system/error_code.hpp>
#include <string>
template<typename Handler1, typename Handler2>
void two_handler_test_func(const char* p1, const char* p2, Handler1 handler1, Handler2 handler2)
{
handler1(boost::system::error_code());
handler2(boost::system::error_code());
}
class MyTestClass
{
public:
template<typename Handler>
void test_func(const std::string& p1, const std::string& p2, Handler handler)
{
two_handler_test_func(p1.c_str(), p2.c_str(),
boost::bind(&MyTestClass::handle_test_func1<Handler>, this, _1, handler),
boost::bind(&MyTestClass::handle_test_func2, this, _1, 2));
}
private:
template<typename Handler>
void handle_test_func1(const boost::system::error_code& ec, Handler handler)
{
handler(ec);
}
void handle_test_func2(const boost::system::error_code& ec, int b)
{}
};
void command_listen_finished(const boost::system::error_code& ec, int i)
{}
void command_listen_finished2(const boost::system::error_code& ec)
{}
class TestCallback
{
public:
TestCallback(int i)
:m_i(i)
{}
void operator()(const boost::system::error_code& ec)
{}
private:
int m_i;
};
int main()
{
MyTestClass testClass;
testClass.test_func("abc", "def", boost::bind(command_listen_finished, _1, 1));
//testClass.test_func("abc", "def", command_listen_finished2);
//testClass.test_func("abc", "def", TestCallback(1));
return 0;
}
The last three "testClass.test_func" call.
The first one can not pass the compile, but the second and thrid can.
I thought the first one bind is build a same functor as the third one, but it just can not pass compile.
I use boost 1.4.3
The compile I use is VC9, the detail version is below:
Microsoft Visual Studio 2008
Version 9.0.30729.1 SP
Microsoft .NET Framework
Version 3.5 SP1
Installed Edition: Enterprise
Microsoft Visual C++ 2008 91899-153-0000007-60183
Thanks for help...
-
September 5th, 2010, 12:19 PM
#2
Re: some problem with boost::bind.
What is the error message?
-
September 5th, 2010, 02:49 PM
#3
Re: some problem with boost::bind.
The problem is that in test_func(), "handler", which is being used in a bind-expression, can itself be a bind-expression (and indeed, it is in the first test_func() call, but not in the second and third).
Boost.Bind treats nested bind-expressions specially to facilitate function composition.
Let's simplify the example a bit and see what's happening:
Code:
#include <boost/bind.hpp>
using boost::bind;
template<typename Handler>
void invoker(Handler handler)
{
handler();
}
template<typename Handler>
void f(Handler handler)
{
handler();
}
template<typename Handler>
void test_func(Handler handler)
{
invoker(bind(&f<Handler>, handler));
}
void g(int i)
{
}
int main()
{
test_func(bind(g, 1));
return 0;
}
I've changed a few names for brevity: invoker is your two_handler_test_func, f is your handle_test_func1 template, and g is your command_listen_finished. I've also removed all the extra parameters so we can focus on the issue.
Normally, if f is a unary function, bind(f, handler) is a nullary function such that bind(f, handler)() is equivalent to f(handler).
Likewise, if g is a unary function, bind(g, 1) is a nullary function such that bind(g, 1)() is equivalent to g(1).
We would thus expect that bind(f, bind(g, 1)) is a nullary function such that bind(f, bind(g, 1))() is equivalent f(bind(g, 1)). If inside f we call handler(), which we do, we would thus expect this to be equivalent to calling bind(g, 1)(), i.e. g(1).
However, boost treats nested bind-expressions specially and actually evaluates nested bind-expressions - so rather than bind(f, bind(g, 1))() being equivalent to f(bind(g, 1)), it is equivalent to f(bind(g, 1)())!
This makes all the difference in the world, since bind(g, 1) was a function, but bind(g, 1)() is the result of that function, in this case void. While it made perfect sense to invoke bind(g, 1) - a function - in invoker(), it makes no sense at all to invoke bind(g, 1)() - a void expression. Hence the compiler error.
Now there is a good reason why Boost.Bind behaves the way it does. f(bind(g, 1)()) is equivalent to f(g(1)), so this special treatment of nested bind-expressions allows us to create a binding that acts as a function composition, something that would be impossible otherwise (try it!). However, in some cases, such as yours, you don't want this behaviour - you don't want Boost.Bind to evaluate the nested bind expression. The solution is "guard" the inner bind expression by wrapping it in a call to boost:: protect(). This tells boost that the inner bind expression should be evaluated, and we get the desired behaviour (you can read all the details here.):
Code:
#include <boost/bind/protect.hpp>
template<typename Handler>
void test_func(Handler handler)
{
invoker(bind(&f<Handler>, boost::protect(handler)));
}
There is however one blemish in this solution. This code still does not compile, because the type of boost:: protect(handler) is different from the type of handler (which is Handler), so there is a mismatch between the type you're instantiating f with (Handler), and the type you are passing to it (boost:: protect(handler)).
One solution is to move the boost:: protect() call to test_func's call site:
Code:
test_func(boost::protect(bind(g, 1)));
Now the Handler template parameter in test_func() is deduced to be the type of boost:: protect(bind(g, 1)) and all is well.
However, this solution doesn't feel right. The fact that test_func() will use the bind-expression we pass to it inside another bind-expression is an implementation detail of test_func(). The caller of test_func() should not have to know to wrap every bind-expression being passed to test_func() in boost:: protect() - this should be test_func()'s responsibility.
So if we want to keep the boost:: protect call in test_func(), we have two options. One is to write a helper function to deduce the type of boost:: protect(handler), like this:
Code:
#include <boost/bind/protect.hpp>
template <typename Handler>
void test_func_helper(Handler handler)
{
invoker(bind(&f<Handler>, handler));
}
template<typename Handler>
void test_func(Handler handler)
{
test_func_helper(boost::protect(handler));
}
The other option is to use BOOST_TYPEOF (or, if you have access to it, the proper C++0x equivalent decltype) to deduce the type of boost:: protect(handler) in test_func:
Code:
#include <boost/bind/protect.hpp>
#include <boost/typeof/typeof.hpp>
template<typename Handler>
void test_func(Handler handler)
{
invoker(bind(&f<BOOST_TYPEOF(boost::protect(handler))>, boost::protect(handler)));
}
Last edited by HighCommander4; September 5th, 2010 at 04:56 PM.
Old Unix programmers never die, they just mv to /dev/null
-
September 6th, 2010, 12:14 AM
#4
Re: some problem with boost::bind.
This is my first time to hear boost:: protect.
I am really leak of careful to read the boost::bind document.
Thank you very much help me to deal with this issue.
-
September 6th, 2010, 01:33 AM
#5
Re: some problem with boost::bind.
But it seems that the boost: rotect can not work with noraml functor.
So if it write in test_func, test_func can not work with normal functor any more.
-
September 6th, 2010, 06:44 AM
#6
Re: some problem with boost::bind.
Looking at this protect implementation:
http://www.boost.org/doc/libs/1_43_0...nd/protect.hpp
I don't see any obvious reason why it wouldn't work with an arbitrary functor. It appears to be a simple wrapper.
If there really is trouble, you could write your own functor wrapper; it would work the same way.
-
September 6th, 2010, 03:02 PM
#7
Re: some problem with boost::bind.
 Originally Posted by Lindley
Looking at this protect implementation:
http://www.boost.org/doc/libs/1_43_0...nd/protect.hpp
I don't see any obvious reason why it wouldn't work with an arbitrary functor. It appears to be a simple wrapper.
If there really is trouble, you could write your own functor wrapper; it would work the same way.
If you look at the first typedef in protect.hpp, it requires the F template parameter to have nested "result_type". For this reason an arbitrary function will not work, nor will an arbitrary functor unless it's derived from std::unary_function, std::binary_function etc.
I can't think of any workaround right now except calling boost:: protect() when you call test_func(), not inside test_func(), but this still feels wrong to me...
Old Unix programmers never die, they just mv to /dev/null
-
September 6th, 2010, 11:02 PM
#8
Re: some problem with boost::bind.
Well, I use boost: rotect to wrap every boost::bind where I want to pass a callback handler.
For a rule I think it could be safety, just a bit grammer trouble.
-
September 7th, 2010, 06:50 AM
#9
Re: some problem with boost::bind.
 Originally Posted by HighCommander4
If you look at the first typedef in protect.hpp, it requires the F template parameter to have nested "result_type". For this reason an arbitrary function will not work, nor will an arbitrary functor unless it's derived from std::unary_function, std::binary_function etc.
I can't think of any workaround right now except calling boost:: protect() when you call test_func(), not inside test_func(), but this still feels wrong to me...
I suppose you could just require functors to be derived from unary_function......that's totally reasonable as a requirement.
However, there's a catch: C++0x lambda functions won't have result_type defined. On the other hand, if you have C++0x support, it might be possible to leverage decltype to work out the result using a wrapper class.
-
September 7th, 2010, 11:08 AM
#10
Re: some problem with boost::bind.
 Originally Posted by Lindley
I suppose you could just require functors to be derived from unary_function......that's totally reasonable as a requirement.
Functors, yes. But it is nice to be able to pass plain functions as callbacks as well.
However, there's a catch: C++0x lambda functions won't have result_type defined. On the other hand, if you have C++0x support, it might be possible to leverage decltype to work out the result using a wrapper class.
Well, if you have C++0x lambdas, you probably don't need to use bind() at all...
Old Unix programmers never die, they just mv to /dev/null
-
September 7th, 2010, 11:18 AM
#11
Re: some problem with boost::bind.
 Originally Posted by HighCommander4
Functors, yes. But it is nice to be able to pass plain functions as callbacks as well.
Well, that's what std: tr_fun is for.
-
September 7th, 2010, 11:52 AM
#12
Re: some problem with boost::bind.
 Originally Posted by Lindley
Well, that's what std:: ptr_fun is for.
Interesting, I didn't know about std:: ptr_fun.
However, since it only transforms pointers to functions into functors (as opposed to anything function-like into a functor), it must still be used at the call site.
I just realized, though, that boost::bind itself can be used as something that transforms anything function-like into a functor. For example, if you know that f is a callback function (whether a plain function, functor, bind-expression, etc.) with 2 parameters, then boost::bind(f, _1, _2) is an equivalent functor.
Using this technique, you can move all the wrappers to where the callback function is used inside a bind-expression:
Code:
template <typename Callback>
void function_that_uses_callback(Callback callback)
{
...
boost::bind(..., boost::protect(boost::bind(callback, _1, _2)));
...
}
and at the call site you are now free to pass in anything you like, including plain funcions without having to wrap them in anything: boost::bind transforms them into a functor, and boost:: protect ensures they are not evaluated.
Last edited by HighCommander4; September 7th, 2010 at 11:54 AM.
Old Unix programmers never die, they just mv to /dev/null
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|