0

explain this one:

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 the covers, to allow the line f11 = f22?

Seems unusual because f11 is a functor whose operator(int) calls an X::foo(int) with a this of x, so it would seem to be type specific to X, and then when I do same with f2/f22 its specific to Y, so how can the line f11 = f22 be allowed?

I actually want to do the f11 = f22 line, but I was surprised to see this was allowed, and am trying to understand how this isnt a type mismatch.

I know, "use the source, Luke", but the Boost sources are difficult to follow/comprehend with everything going on in there.

If you could, for your answer, show the classes that are expanded by the templatization, that would help.

I was thinking it was getting away with this by void* casting or something like that, but that seems like a hack, and beneath Boost to stoop down to that level. So whassup?

BTW, if you haven't encountered this kind of thing before, you should at least be surprised at this bit of magic, since you can't say "x = y" above, as that would clearly be disallowed since they are different types and there is no X::operator=(Y&), so that's where this amazement comes from - its a clever piece of subterfuge.

David Neiss
  • 8,161
  • 2
  • 20
  • 21
  • I personally don't see any type safety issue here. `boost::function` and `std::function` can wrap an object of an abstract type that has a templated subclass to match the type of the assigned object. I'm not sure I'd be personally able to produce an example but I'm pretty sure that's how it works. Notice that the template arguments to `boost::function` don't specify what kind of object it should wrap. – zneak Sep 21 '13 at 02:09
  • Thanks, but Im specifically looking for the expanded functors to understand it. I guess its not so much type safety, but type incompatible. Yea, I did notice that about the template arg, its just not making total sense until I can see the expanded code. – David Neiss Sep 21 '13 at 02:15

1 Answers1

3

The technique used is known as type erasure.

I'll demonstrate with a toy class, written in C++11. Many of the 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 being a template class isn't the 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 construct the nullary_func with, and then stores a pointer to the custom typed object's abstract base. This abstract base has a virtual method of some kind that we call. The custom child type implements that virtual method, and invokes () on the underlying type.

Now, the shared_ptr is probably not what boost uses (probably some kind of value_ptr), and in function you have both a template class and template constructor, and there are lots of other details. And you can implement your own dynamic dispatch instead of virtual calls if you want to get fancy. And in real C++11 I'd be doing perfect forwarding with all the issues of a perfect forwarding single argument constructor that entails.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • 1
    Ok, I'll take that. With some more googling on "type erasure" & boost functional leads me to see that something such as this is going on. So as I understand it, one way to achieve this type erasure is to create a base class having the desired interface and then instantiating derived classes from that base which implement the interface, but in their specific implementations for the virtual function, redirect to their specific types. – David Neiss Sep 21 '13 at 04:22
  • After trying to look at more boost source to ensure that the technique for type erasure was virtual functions, Im seeing some contradicting evidence around line 120 of function_base.hpp: // For bound member pointers struct bound_memfunc_ptr_t { void (X::*memfunc_ptr)(int); void* obj_ptr; } bound_memfunc_ptr; So there is some void* stuff going on in here. Although upon further grepping, I dont see references to bound_memfunc_ptr, so the mystery deepens. – David Neiss Sep 22 '13 at 15:46
  • SO's http://stackoverflow.com/questions/5450159/type-erasure-techniques suggests that functional isn't virt function but void* as well. – David Neiss Sep 22 '13 at 15:52
  • They are doing manual dynamic dispatch. @DavidNeiss -- basically implementing limited `virtual` manually, probably in a more cache friendly way. It is a micro optimization. – Yakk - Adam Nevraumont Sep 22 '13 at 16:43
  • so I think you are saying they are just doing a void* kind of thing, which is like the void (X:*memfunc_ptr)()) stuff is above, where X is just a stand-in for a kind of > (to borrow from Java) kind of template specialization. If that were the case, it would just be holding the pointer to function within itself (as an X*), which would allow the operator=(?>) w/o type conflicts. Where is the cache friendliness - that it doesn't have to go to the vtable to do the lookup, so one less indirection? – David Neiss Sep 22 '13 at 20:10
  • @davidneiss mayhap. There are a bunch of "the fastest possible c++ delegate" attempts via Google. I have not examined the details of what boost did, it just looks similar to what I have seen such optimizations involve. The basic technique, writing code that is dependent on the type constructed from, using an abstract interface without that type detail, remains the same. The `virtual` technique above is just the simplest and least fiddly way to do it. – Yakk - Adam Nevraumont Sep 22 '13 at 22:15