Code Walk-through: How exactly does bind work (with placeholders) ?
I was debugging a piece of code and hit that all-too-common situation again where though I could fix the problem but could not understand why compiler generated that particular error message from boost::bind library. So just out of curiosity, I decided to understand boost::bind implementation. Googled for it and searched stackoverflow without any real answer and decided to trace through the code for a simple example.
Lets take this example :
class MyArg
{
public:
explicit MyArg(int i) : i_(i) {}
int get_i() const
{
return i_;
}
private:
int i_;
};
void f(MyArg a, MyArg b)
{
cout << "a = " << a.get_i() << " b = " << b.get_i() << endl;
}
int main()
{
MyArg obj_1(11), obj_2(22);
bind(f, _1, obj_2)(obj_1);
return 0;
}
Now check the code that is being used for this particular example with g++ 4.3.0 on linux. I have removed everything else other than the code required for the above example including all the portability features. I am sure I might be missing a lot of the tricks and optimizations by pruning it this way, but still it gave some insights that may help me debug my programs in the future. Do not include boost::bind to run the following piece of code, as I have not changed any of the names, but only got them out of boost namespace.
///placeholders
template<class T>
struct is_placeholder
{
enum _vt { value = 0 };
};
template<int I>
struct arg
{
arg(){}
template<class T>
arg ( T const& )
{
typedef char T_must_be_placeholder[ I == is_placeholder<T>::value ? 1: -1 ];
}
};
template<int I>
struct is_placeholder< arg<I> >
{
enum _vt { value = I } ;
};
template<int I>
bool operator==( arg<I> const&, arg<I> const&)
{
return true;
}
template<int I>
struct is_placeholder< arg<I>(*)() >
{
enum _vt { value = I } ;
};
///value
template<class T>
class value
{
public:
value(T const & t): t_(t) {}
T & get() { return t_; }
T const & get() const { return t_; }
bool operator==(value const & rhs) const
{
return t_ == rhs.t_;
}
private:
T t_;
};
////storage
template<class A1>
struct storage1
{
explicit storage1(A1 a1):a1_(a1) {}
A1 a1_;
};
template<class A1, class A2>
struct storage2: public storage1<A1>
{
typedef storage1<A1> inherited;
storage2( A1 a1, A2 a2 ): storage1<A1>( a1 ), a2_( a2 ) {}
A2 a2_;
};
///list1
template< class A1 >
class list1: private storage1< A1 >
{
private:
typedef storage1< A1 > base_type;
public:
explicit list1( A1 a1 ): base_type( a1 ) {}
A1 operator[] (arg<1>) const
{
return base_type::a1_;
}
A1 operator[] (arg<1> (*) ()) const
{
return base_type::a1_;
}
template<class T> T & operator[] ( value<T> & v ) const
{
return v.get();
}
};
// unwrap
template<class F> struct unwrapper
{
static inline F & unwrap( F & f, long )
{
return f;
}
};
//type
template<class T>
struct type{};
//list2
template< class A1, class A2 > class list2: private storage2< A1, A2 >
{
private:
typedef storage2< A1, A2 > base_type;
public:
list2( A1 a1, A2 a2 ): base_type( a1, a2 ) {}
template<class F, class A> void operator()(type<void>, F & f, A & a, int)
{
unwrapper<F>::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_]);
}
};
//result_traits
template<class R, class F> struct result_traits
{
typedef R type;
};
//bind_t
template<class R, class F, class L>
class bind_t
{
public:
typedef bind_t this_type;
bind_t(F f, L const & l): f_(f), l_(l) {}
typedef typename result_traits<R, F>::type result_type;
template<class A1>
result_type operator()(A1 & a1)
{
list1<A1 &> a(a1);
return l_(type<result_type>(), f_, a, 0);
}
private:
F f_;
L l_;
};
//add_value
template < class T, int I>
struct add_value_2
{
typedef arg<I> type;
};
template<class T>
struct add_value_2<T, 0>
{
typedef value<T> type;
};
template<class T>
struct add_value
{
typedef typename add_value_2<T, is_placeholder< T >::value >::type type;
};
template<class A1, class A2>
struct list_av_2
{
typedef typename add_value<A1>::type B1;
typedef typename add_value<A2>::type B2;
typedef list2<B1, B2> type;
};
template<class R, class B1, class B2, class A1, class A2>
bind_t<R, R (*) (B1, B2), typename list_av_2<A1, A2>::type>
bind( R (*f) (B1, B2), A1 a1, A2 a2)
{
typedef R (*F) (B1, B2);
typedef typename list_av_2<A1, A2>::type list_type;
return bind_t<R, F, list_type> (f, list_type(a1, a2));
}
static arg<1> _1;
class MyArg
{
public:
explicit MyArg(int i) : i_(i) {}
int get_i() const
{
return i_;
}
private:
int i_;
};
void f(MyArg a, MyArg b)
{
cout << "a = " << a.get_i() << " b = " << b.get_i() << endl;
}
int main()
{
MyArg obj_1(11), obj_2(22);
bind(f, _1, obj_2)(obj_1);
return 0;
}
Explanation of the code above :
1. In bind you use placeholders for parameters to be supplied later. Placeholders are really variables in global namespace of type arg<T> where T is integral type giving you the position of the actual argument to be passed.
2. So as seen above bind is a templated function returning a bind_t instance.
3. But before bind returns bind_t instance, list_av_2 lets you create a list_type.
4. To get the list_type, which is of type list2<B1, B2>, list_av_2 derives B1 and B2 from original argument types A1 and A2. In the example A1 is arg<1> and A2 is MyArg.
5. list_av_2 uses struct add_value to derive B1 and B2. B1 remains arg<1> but B2 becomes value<MyArg>
6. arg_value uses add_value_2<T, I> where I is the integral template parameter of arg<I>.
7. To get the integer template parameter of arg<I>, arg_value_2 uses is_placeholder<T>::value. is_placeholder<T> is partially specialized for is_placeholder<arg<I> >. The partial specialization of is_placeholder<T> for arg<I> would assign I to is_placeholder<T>::value, otherwise for any other type is_placeholder<T>::value will just be 0.
Now from step 3 is_placeholder<A1>::value will be 1 and is_placeholder<A2>::value will be 0.
8. By the way, is_placeholder is also used in the copy constructor of arg<I> :
typedef char T_must_be_placeholder[ I == is_placeholder<T>::value ? 1: -1 ];
The above statement makes sure that arg<I> can be copy constructed from another arg<I> of exactly the same type and with the same value of I; otherwise you get mysterious eror message like:
“error: creating array with negative size (‘-0×000000001’)”.
9. Now from 6 in add_value, add_value_2 is used with add_value_2<T, 1> for A1 which is arg<1> and add_value_2<T, 0> for A2 which is MyArg, respectively. add_value_2<T, 0> is partially specialized so that add_value_2<T,0>::type becomes value<T>, while add_value_2<T,1>::type remains arg<1>.
10. To return back to step 3, we get list_type which is list2< arg<1>, value<MyArg> >. To instantiate bind_t, a list2< arg<1>, value<MyArg> > instance is created with a1 and a2 where a1 is _1 and a2 is obj_2.
11. list2 is inherited from storage2<A1, A2>. While there was not anything interesting in list2 constructor, we must check storage2<A1, A2>. storage2<A1, A2> is inherited from storage1<A1>. While constructing storage2<A1, A2> stores a2 a member variable but passes a1 to storage1<A>. bind/storage.hpp follows the same pattern with any number of arguments. So storage3<A1, A2, A3> is inherited from storage2<A1, A2> and stores a3 as a member variable while passing on a1 and a2 to storage2. This way the order of argument evaluation could be kept fixed. By the way, we have ommited partial specialization of storage for arg<I> and arg<I>(*).
12. At last we create an instance of type :
bind_t<void, void(*)(MyArg, MyArg), list2< arg<1>, value<MyArg> >
to be returned back to bind caller.
13. Now comes the second part of invoking the binder with obj_1 as an argument. This will naturally invoke bind_t::operator()() which is being passed actual argument obj_1. bind_t<R, F, L>::operator()(A1) stores the parameter in list1<A1&> which as we have seen above, let storage1<A1> store the object.
14. Next it invokes, list2<arg<1>, value<MyArg> >::operator()() with stored function pointer, and the list1<MyArg&>. In the above code we kept only the function specialization for type<void>. Here the most interesting part involves calling list1<MyArg&> subscript operator list1<MyArg&>::operator[] with a1 and a2 from base_type or storage2.
15. So we go back to check the subscript operator implementation of list1 which is really an overloaded implementation where:
— If the paramter is of type arg<1> then you get the value stored in list1 which is list1::base_type::a1_.
— But if the parameter is of type value<T>, v.get() is returned.
Now when we call a[base_type::a1_], base_type::a1_ is really _1 of type arg<1>, so the subscript operator returns the value stored in list1 which we just stored in step 13 when we instantiated list1 with the real argument passed to operator()().
On the other hand a[base_type::a2_] is of type value<MyArg> so that returns the object stored in value<T>, which is the one we passed when bind was called.