Wednesday, November 21, 2012

movable std::bind with placeholders

And finally the placement compatible version of the movable binder.. from 2012/11/perfect-forwarding-bind-compatable-with.html. this is again using threads to show that it works as with move only objects.

Also this probably needs gcc to work as it has a special placeholder classes call std::_Placeholder<>. Also I had to work around a compiler bug that kills the variadic templates nasty little monster that one..

And I still havent done anything about the problem with copying the binder

Ok this code probably needs a bit of explanation
There are several parts to a binder/lambda
  1. The "Binder Object" this is the main body of the binder. in the following code its called "MovableBinder"
  2. The "Bound function" this is the function that is to be executed in at the later point. In the code below its the typedef or template param called "Func"
  3. The "Closure" this is the part of the binder that captures variables in the context that the binder was created in. In the code below its called "MoveCapture"
  4. The "Placeholder" these are placement markers that allow the parameters that are handed in to be jumbled around for the binding interface. In the below code i attempted to use standard std::placeholders. It hasnt really worked out so great.
  5. The "Parameters" these are the params passed to the bind at the point of its execution. In the following code they are cached into the "MoveHolder" class.
This main core of this code design is the MoveCapture.

On Construction it recurses down the varable param decimates the list to an aggregate object that caches 1 param "A" or the placeholder on each step.

On "call" it again recurses down the MoveCapture structure again but this time its its doing the exact opposite and inductively constructing a list of params into the variadic param list called "InArgs".. If it strikes a placeholder it simple retrieves that entry from the MoveHolders object. By the end of structure it has recreated the full param list for the bound function call.


#include <iostream>
#include <memory>
#include <utility>
#include <tuple>
#include <thread>
#include <functional>
#include <string>

template <typename ...H>
struct MoveHolder 
{
  typedef std::tuple<H...>  Tuple;
  Tuple values_;
  
  MoveHolder(H&& ...h) :
    values_(std::forward<H>(h)...)
  {}

  template <int I>
  typename std::tuple_element<I, Tuple>::type&& access()
  {
    return std::move(std::get<I>(std::move(values_)));
  }
};


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))
  {
    std::cout << "DEBUG " << __LINE__ << "\n";
  }

  template <typename Func,
     typename H,
       typename ...InArgs>
  void call(Func& f,
       H&& holders,
       InArgs&& ...args)
  {
    std::cout << "DEBUG " << __LINE__ << "\n";

    MoveCapture<Args...>::call(f, 
          holders,
          std::forward<InArgs>(args)...,
          std::move(a_));
  }
};

template <typename A> 
struct MoveCapture<A>
{
  A a_;

  MoveCapture(A&& a) :
    a_(std::forward<A>(a))
  {
    std::cout << "DEBUG " << __LINE__ << "\n";
  }

  template <typename Func,
     typename H,
       typename ...InArgs>
  void call(Func& f,
       H& holders,
       InArgs&& ...args)
  {
    std::cout << "DEBUG " << __LINE__ << "\n";

    f(std::forward<InArgs>(args)...,
      std::move(a_));
  }
};

template <int I, typename ...Args>
struct MoveCapture<const std::_Placeholder<I>, Args...> : public MoveCapture<Args...>
{
  enum { Index = I };
  
  MoveCapture(const std::_Placeholder<I>& h, Args&& ...args) :
    MoveCapture<Args...>(std::forward<Args>(args)...)
  {
    std::cout << "DEBUG " << __LINE__ << "\n";
  }

  //WARNING complier BUG!
  // COMMENTING THIS WILL CAUSE "call" to fail build
  template <typename H>
  void access(H& holders)
  {
    std::cout << I << "\n";
  }
  //END BUG

  template <typename Func,
     typename H,
       typename ...InArgs>
  void call(Func& f,
       H& holders,
       InArgs&& ...args)
  {
    std::cout << "DEBUG " << __LINE__ << "\n";

    MoveCapture<Args...>::call(f,
          holders,
          std::forward<InArgs>(args)...,
          std::move(holders.access<I-1>()));
  }
};

template <int I>
struct MoveCapture<const std::_Placeholder<I>>
{
  MoveCapture(const std::_Placeholder<I>& h) 
  {
    std::cout << "DEBUG " << __LINE__ << "\n";    
  }

  //WARNING complier BUG!
  // COMMENTING THIS WILL CAUSE "call" to fail build
  template <typename H>
  void access(H& holders)
  {
    std::cout << I << "\n";
  }
  //END BUG

  template <typename Func,
     typename H,
       typename ...InArgs>
  void call(Func& f,
       H& holders,
       InArgs&& ...args)
  {
    std::cout << "DEBUG " << __LINE__ << "\n";

    f(std::forward<InArgs>(args)...,
      std::move(holders.access<(I-1)>()));
  }
};

//template <typename ...P>
template <typename Func, typename Capture>
class MovableBinder
{
private:
  Func func_;
  Capture* capture_;

public:
  MovableBinder(Func func, Capture* cap) :
    func_(func),
    capture_(cap)
  {}

  ~MovableBinder()
  {
    if (capture_!= NULL)
      delete capture_ ;
  }

  template <typename ...V>
  void operator()(V&& ...v)
  {
    std::cout << "DEBUG " << __LINE__ << "\n";
    MoveHolder<V...> holders(std::forward<V>(v)...);
    capture_->call(func_, holders);
  }
};

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;

    std::cout << "DEBUG " << __LINE__ << "\n";
    Capture* capture =  new MoveCapture<H...>(std::forward<H>(h)...);
    return MovableBinder<Func,Capture>(f,capture);
  }
};

void hello(double&& done)
{
  std::cout << done << "\n";
}  

void hello2(double&& done1,
     double&& done2)
{
  std::cout << " part1:" << done1 
     << " part2:" << done2 
     << "\n";
}  

void thread_core() 
{
  std::this_thread::sleep_for(std::chrono::seconds(2));
}

void join_thread(std::thread&& t1)
{
  std::cout << "Joining the thread....\n";
  t1.join();
}

int main()
{
  std::cout << "******** MANUAL USAGE **********\n";
  
  MoveCapture<const std::_Placeholder<1> > capture(std::placeholders::_1);
  MoveHolder<double> param(1.23);
  std::cout << param.access<0>() << "\n";
  
  capture.call(hello, param);
  
  std::cout << "******** NORMAL USAGE *********\n";
  
  auto movable_binder_1a = MovableSetup<double>::setup<const std::_Placeholder<1> >(hello,std::placeholders::_1);
  movable_binder_1a(1.34);

  auto movable_binder_1b = MovableSetup<double>::setup(hello,3.14);
  movable_binder_1b();

  auto movable_binder_2a = 
    MovableSetup<double, double>::setup<const std::_Placeholder<1>,
       const std::_Placeholder<2>>
          (hello2, std::placeholders::_1, std::placeholders::_2);
  movable_binder_2a(4.56,1.37);

  auto movable_binder_2b = 
          MovableSetup<double, double>::setup<double,
           double>
          (hello2,492.54, 743.293);
  movable_binder_2b();

  auto movable_binder_2c = 
          MovableSetup<double, double>::setup<double,
       const std::_Placeholder<2>>
          (hello2,492.54, std::placeholders::_2);
  movable_binder_2c(4.56,1.37);  //trash param 1

  std::cout << "******** THREAD BIND USAGE *********\n";

  std::thread tc(thread_core);
  auto movable_binder_tc = 
    MovableSetup<std::thread>::setup(join_thread, std::move(tc));
  movable_binder_tc();

  std::thread td(thread_core);
  auto movable_binder_td = 
    MovableSetup<std::thread>::setup<const std::_Placeholder<1> >(join_thread, std::placeholders::_1);
  movable_binder_td(std::move(td));

}

No comments:

Post a Comment