Boost :: function <void (int)> type compatibility, how is this done?
explain this:
struct X { void foo(int arg) { cout << "X" << arg << endl; } }; struct Y { void bar(int arg) { cout << "Y" << arg << endl; } }; int main(int argc, char *argv[]) { X x; Y y; mem_fun1_t<void, X, int> f1 = std::mem_fun(&X::foo); boost::function<void (int)> f11 = std::bind1st(f1, &x); f11(2); mem_fun1_t<void, Y, int> f2 = std::mem_fun(&Y::bar); boost::function<void (int)> f22 = std::bind1st(f2, &y); f22(2); f11 = f22; // WOW, THIS IS ALLOWABLE } How does Boost work under covers to allow the line f11 = f22?
It seems unusual because f11 is a functor whose operator (int) calls X :: foo (int) with this x character, so it seems to be specific to X, and then when I do the same with f2 / f22, characteristic of Y, so how can the line f11 = f22 be allowed?
I really want to make the line f11 = f22, but I was surprised to see that this is allowed, and trying to understand how this is not a type mismatch.
I know, “use the source, Luke,” but Boost sources are hard to follow / understand everything that happens there.
If you could, for your answer, show classes that expand by templatization, this will help.
I thought it was going away from this with void * casting or something like that, but it seems like a hack under Boost too, to go down to that level. So what?
By the way, if you have not encountered similar things before, you should at least be surprised at this magic, since you cannot say "x = y" above, since it would be explicitly forbidden, since they are different types, and there is no X :: operator = (Y &), so where this amazement comes from is the smart part of the trick.
The technique used is known as erasing styles.
I will demonstrate a toy class written in C ++ 11. Many details will not be accurate, but the general technique is:
struct nullary_impl { virtual void invoke() const = 0; virtual ~nullary_impl() {} }; typedef std::shared_ptr<nullary_impl> nullary_pimpl; struct nullary_func { nullary_pimpl pimpl; nullary_func() = default; nullary_func( nullary_func const& ) = default; template<typename F> nullary_func( F const& f ); void operator()() const { pimpl->invoke(); }; }; template<typename T> struct nullary_impl_impl:nullary_impl { T t; virtual void invoke() const override { t(); } nullary_impl_impl( T const& t_ ):t(t_) {} }; template<typename F> nullary_func::nullary_func( F const& f ): pimpl( std::make_shared( nullary_impl_impl<F>(f) ) {} now in this case the class is not a template class, but it is a template class is not an important part.
The important part is that it has a template constructor. This template constructor creates a custom typed object based on what type we are building nullary_func with, and then saves a pointer to the abstract base of the user typed object. This abstract base has a virtual method that we call. A custom child type implements this virtual method and calls () on the base type.
Now shared_ptr is probably not using boost (maybe some kind of value_ptr ), but in the function you have both the template constructor and the template constructor, and there are many other details. And you can implement your own dynamic mailing instead of virtual calls, if you want a fantasy. And in real C ++ 11, I would do the perfect forwarding with all the problems of the perfect single argument constructor that entails.