Wednesday, December 5, 2012

Lecture notes for the design of the std::bind replace

Normally when I blog its for myself, this isn't profession programming, this is hack and slash programming at its best. I normally wander around a topic until I get board of it. Blog what I complete and move on.. I have hundreds of half written posts, incomplete code fragments, scratch notes and todo'ed ideas.. eh maybe someday ill get to them... And my blog posts are normally min on the explanation and max on the code rampage...

Unfortunately sooner or later you get asked to present something would form a logically concise and worth while presentation. and somehow the excuse "But Im not anything more that am just another com sci nut with a blog and to much free time.." doesn't work...

So here is the are the notes from my presentation on the std::bind replacement(in a non-ADHD form)

The conversion of current std::bind implementations into movable form and lessons learned in the process.


Intro


As the old saying goes it is often the journey that matters and not the destination. In this generation we tend to get distracted easily and fail to understand the point of taking the harder route since the destination is the same anyway. Engineering and Science is one of those "arts" that often benefit from taking the harder route. Make no mistake engineering and science is an art.. it can be the industrial bare bones application(like a 1970 "mobile" phone) or something as refined as an iPhone. The key difference is the engineer that created the piece of work, his know-how and his skill at applying it. By reinventing the wheel once in a while we engineers can often pick up on the original basis of the problem and the foundation of solution and with this new insight and knowledge read into the mind of the original designers, metaphorically pick his brain and re-imagine the solution into one suitable for a comparable problem.

Which is what i have done in a series of blog posts here

The problem

The Issue at hand: Currently std::bind and Lamdba’s don’t seem to be move compatible. The basic problem to we are discussing here is a next step logical step to perfect forwarding. That is a "Delayed perfect forwarding" or “Indirect perfect forwarding”. The idea of "perfect forwarding" is to determine if the object is moving or not and pass it on as such. "Delayed perfect forwarding" would be the idea performing a perfect forward move through to function in a delay lazy calling content. This effectively means “binding ”. Eg we move the function and current contexts data into the bind capture and move it out of the capture context at the actual moment of execution and fill in the last minute details. Current Lambda disallows this:
std::function<void ()> && evil_move_capture()
{
  std::thread t(thread_core);
  std::function<void ()> func = 
  [std::move(t)()]->void 
  {
    std::cout << "Joining the thread....\n";
    t.join();
  };

  func();
}
Resulting in these compile issues:
lambda_move.cpp: In function ' std::function<void ()> && evil_move_capture()':

lambda_move.cpp:17:6: error: capture of non-variable 'std'

<built-in>:0:0: note: 'namespace std { }' declared here
You can of course move on execution... but that misses the point of the capture vs parameters
std::function<void ()> && evil_create_func()
{
  std::thread t(thread_core);

  std::function<void ()> func = 
  [&]()->void 
  {
    std::thread t0(std::move(t));
    std::cout << "Joining the thread....\n";
    t0.join();
  };

  //works here but after return! 
  //func();

  return std::move(func);
}
And of course you can’t seem to do it with the current bind implementation. (Although it has been suggested to me that this a mistake in the compilers/libraries implementation and not the new standard)
std::function<void ()>&& evil_bind_capture()
{
  std::thread t(thread_core);
  //to check that its going to work normally
  //bind_f(std::move(t));

  auto func = std::bind(bind_f, std::move(t));
  func();
}
…/c++/4.7.0/functional:1206:35: error: cannot bind 'std::thread' lvalue to std::thread&&'

The solution

The simplest solution is to move into the capture context and then to always move out of it. Of course this misses some of the more subtle points but who cares.

Step 1: Create an inspectable class for the Captured data.

The basic reproduction of a tuple. std::tuple is the normal std declared by member object... reusing this idea will allow you to create more complex member inspection and self-documenting/self traiting class. Ie Im just doing this so that I can expand its functionally after the fact.. Points to note
  • This is a normal recursive implementation of a varaidic template. Creating an inspectable self-declaring object that is capable of known what its members are.
    • It removes a single param out of the list in params and using it as the main member of the struct and deriving from the recursive construct of the remaining.
  • The constructor accepts its params via std::move and performs perfect forwarding on them into the local storage.
The simplistic version is;
template <typename A, typename ...Args>
struct Obj : public Obj<Args...>
{};
The actual application is:
//The general case
template <typename A, typename ...Args>
struct MoveCapture : public MoveCapture<Args...>
{
  A a_;

  MoveCapture(A&& a, Args&& ...args) :
    MoveCapture<Args...>(std::forward<Args>(args)...),
    a_(std::forward<A>(a))
  {}
};

//The terminal case
template <typename A>
struct MoveCapture<A>
{
  A a_;

  MoveCapture(A&& a) :
    a_(std::forward<A>(a))
  {}
};

Step 2: Expand the Capture class for retrieval

Now this stores the values. The problem of course is how to then retrieve them. This is the where the tuple rebuild comes into play. Points to note.
  • This is one of the another interesting variadic template techniques. Lets dub it "Parameter induction"
    • This is the idea of appending parameters to a list of variadic parameters instead of reducing it.
    • The control mech for this is of course the original reduction that is used when creating the object in the first place. The key difference is that the inducted list of outgoing objects does not have to match the original input list of objects.
Parameter induction. The simplistic version is;
Template <...I>
Control<R,RR....>::induct(I ....i)
{
  R r;
  Control<RR...>::induct(i..., convert(r));
};
The actual application is:
//The general case
template <typename A, typename ...Args>
struct MoveCapture : public MoveCapture<Args...>
{
  A a_;
  ...

  template <typename Func, typename ...InArgs>
  void call(Func& f, InArgs && ...args)
  {
    MoveCapture<Args...>::call(f,
    std::forward(args)...,
    std::move(a_));
  }
  ...
};

//The terminal case
template <typename A>
struct MoveCapture<A>
{
  A a_;
  ...

  template <typename Func, typename ...InArgs>
  void call(Func& f, InArgs && ...args)
  {
    f(std::forward<A>(args)...,
      std::move<A>(a_));
  }
  ...
};

Step 3a: Handling the early Capture of Placeholders

Now we can get the data in and out... But the next problem in the design is how do you handle the difference between a placeholder and the actual data being captured. Primary this just means that the placeholder type and object will appear in the parameter list. So in theory all you have to do is specialize the object to the placeholder as such Points to note:
  • This might on the surface seem simple but this one of more tricky items to make work
  • Pay special care to the “const” when using std::moved item. This is a nasty little trick that allows the const to slip around resulting in massive compiler errors and are very difficult to figure out.
Simplest usage
Template <R,…RR>
struct A {};

Template <special,…RR>
struct A<recreate<special>::type, RR...> {};
The actual application is:
template <int I, typename ...Args>
struct MoveCapture<const std::_Placeholder<I>, Args...> : public MoveCapture<Args...>
{};

Step 3b: Handling the resolution of early Captured and later passed Params

Further more there is a yet another catch 22 when you try to actually resolve out placeholders. You end up having 2 variable param lists to handle and the c++11 spec allows for only 1. So here is another interesting technique, variadic template chaining. Points to note:
  • Since the spec allows 1 list the only way to do this is via a list separator.. in this case that just happens to be a "struct" or "class" object.
  • It is probably wise to end this in a function call for reasons ill explain latter.
The simplistic form.
A<...X>::B<....Y>::C<...Z>::call<>(D...)
The actual application: Additional Points to note:
  • In this design the application of it is actually early. This one is in the constructor of the binder so that the objects are handed to the binder(possibly containing placeholders) and the objects forwarded on to the end call can differ in types.
template <typename ...P>
struct MovableSetup
{
  typedef void (*Func)(P&&...);

  template <typename ...H>
  static MovableBinder<Func,MoveCapture<H...>> setup(Func f, H ...h)
  {
    typedef MoveCapture<H...> Capture;
    Capture* capture =  new MoveCapture<H...>(std::forward<H>(h)...);

    return MovableBinder<Func,Capture>(f,capture);
  }
};

Step 4: Something for the future. A Compiler param deductor

Now the final lesson to learn(but not applied) is that there is a slight difference between the class or struct usage of the varadic template and the function usage of the varadic templates. This creates a sizable difference in the end usage and more importantly the ability of the usage. Points to note
  • In the case of functions they have the capability of deducing the types from the instances and forwarding that into the template. In contrast with as the struct or class type there is no instance of variables so it cant guess the type you have to tell it explicitly.
    • This means that if you wish to create type deducting systems from instantiated objects you will need to create a function wrapper to make it work auto-magically
  • This is why I suggested that you end your chained variadic templates with a function call so that it can deduce as much as possible and save you and the end user of the code the effort.
In the simplistic form its:
struct A<P....>
{};

A<....P> maker(P....p) { return new A<P…>(p…); }