суббота, 22 мая 2010 г.

Анатомия boost::bind #2

Продолжение истории про boost::bind, начатой тут

Итак, после всех наших усилий у нас появилась функция bind, способная обслуживать вызовы такого вида:
int f4(int a, int b, int c, int d)
{
return a + b + c + d;
}
//...
bind(&f4, 1, _1, _2, _3)(2, 3, 4);
bind(&f4, 1, 2, 3, _1)(4);
Самое интересное, что она прекрасно будет работать как с указателями на свободные функции, так и с функторами - ничего специально предпринимать не придется :-)

Теперь настает черед указателя на член класса. Тут уже все сложнее, требуется предварительная подготовка...

Как минимум, нужно научиться по набору типов различать свободные функции, функторы, указатели на функции-члены класса и просто члены данных.
Естественно, опять приходится прибегать к шаблонам.
Объявим наш "словарь":
enum { functor, pointer, reference, dm_reference, dm_pointer };
В результате хочется получить что-то вида
template
struct bind_obj_4<F, A1, A2, A3, A4, 4>
{
function_wrap_4<F, A1, A2, A3, A4, ??> f_;

bind_obj_4(F f, typename c_arg<A1>::type a1, typename c_arg<A2>::type a2, typename c_arg<A3>::type a3, typename c_arg<A4>::type a4): bind_obj_4_base<F, A1, A2, A3, A4>(f, a1, a2, a3, a4)
{
}

typename result_of<F>::type operator()(typename c_arg<A1>::type a1, typename c_arg<A2>::type a2, typename c_arg<A3>::type a3, typename c_arg<A4>::type a4)
{
return f_(
h1_(a1, a2, a3, a4),
h2_(a1, a2, a3, a4),
h3_(a1, a2, a3, a4),
h4_(a1, a2, a3, a4)
);
}
};
Дальше начинается небольшая порция "черной магии С++":
template <typename T>
struct type_2_type
{
typedef T OriginalType;
type_2_type(){} // VC7
};

template<typename T>
struct traits
{
typedef char (&yes)[2];
typedef char (&no)[1];
template<typename C>
static yes probe_ptr(type_2_type<C*>);
template<typename C>
static yes probe_ptr(type_2_type<const C*>);
static no probe_ptr(...);
template<typename C>
static yes probe_ref(type_2_type<const C&>);
template<typename C>
static yes probe_ref(type_2_type<C&>);
static no probe_ref(...);
template<typename R, typename C>
static yes probe_member(type_2_type<R C::*>);
static no probe_member(...);
template<typename R, typename C>
static yes probe_member_fun(type_2_type<R (C::*)()>);
template<typename R, typename C, typename CA1>
static yes probe_member_fun(type_2_type<R (C::*)(CA1 a1)>);
template<typename R, typename C, typename CA1, typename CA2>
static yes probe_member_fun(type_2_type<R (C::*)(CA1 a1, CA2 a2)>);
template<typename R, typename C, typename CA1, typename CA2, typename CA3>
static yes probe_member_fun(type_2_type<R (C::*)(CA1 a1, CA2 a2, CA3 a3)>);
template<typename R, typename C, typename CA1, typename CA2, typename CA3, typename CA4>
static yes probe_member_fun(type_2_type<R (C::*)(CA1 a1, CA2 a2, CA3 a3, CA4 a4)>);
static no probe_member_fun(...);
enum
{
is_member = sizeof(probe_member(type_2_type<T>() ) ) == sizeof(yes),
is_member_fun = sizeof(probe_member_fun(type_2_type<T>() ) ) == sizeof(yes),
is_reference = sizeof(probe_ref(type_2_type<T>() ) ) == sizeof(yes),
is_pointer = sizeof(probe_ptr(type_2_type<T>() ) ) == sizeof(yes)
};
};
Несмотря на устрашающий внешний вид, использовать шаблон очень просто - берем тип, подставляем его в качестве параметра шаблона, а константы is_member, is_member_fun, is_reference, is_pointer дадут нам достаточно полную информацию о его свойствах, которую можно использовать в других местах.

Мы используем факт, что аргумент(...) при подборе компилятором "наилучших" параметров будет рассматриваться последним и проверяем тип результата возвращаемого значениея на совпадение размера с правильным ответом. Обращаю внимание на то, что используется sizeof - в этом случае код не генерируется, для probe_* не нужно определять даже тела, достаточно объявления.

Полученный шаблон позволяет нам дописать недостающее звено в определении f_.

Сначала сделаем себе еще один вспомогательный инструмент, аналогичный оператору if, но работающий на этапе компиляции:
template<int N, int N1, int N2>
struct const_selector
{
enum { result = N2 };
};

template<int N1, int N2>
struct const_selector<1, N1, N2>
{
enum { result = N1 };
};
В зависимости от значения первого параметра шаблона в result попадает второе либо третье значение.
Теперь можно сделать финальное построение:
template<typename F, typename A> struct func_type_selector
{
enum { result =
const_selector<
traits<F>::is_member_fun,
const_selector<traits<A>::is_reference, reference, pointer>::result,
const_selector<
traits<F>::is_member,
const_selector<traits<A>::is_pointer, dm_pointer, dm_reference>::result,
functor
>::result
>::result };
};
Мы получили шаблон, который по двум параметрам в состоянии сказать нам, с чем приходится иметь дело.

Финальный тип f_ выглядит так:
function_wrap_4<F, A1, A2, A3, A4, func_type_selector<F, A1>::result> f_;


Реализация специализаций получается просто тривиальная:
template<typename F, typename A1, typename A2, typename A3, typename A4, int N> struct function_wrap_4
{
F f_;
function_wrap_4(F f): f_(f)
{
}

template<typename CA1, typename CA2, typename CA3, typename CA4>
typename result_of<F>::type operator()(CA1 a1, CA2 a2, CA3 a3, CA4 a4)
{
return f_(a1, a2, a3, a4);
}
};

template<typename F, typename A1, typename A2, typename A3, typename A4> struct function_wrap_4<F, A1, A2, A3, A4, pointer>
{
F f_;
function_wrap_4(F f): f_(f)
{
}

template<typename CA1, typename CA2, typename CA3, typename CA4>
typename result_of<F>::type operator()(CA1 a1, CA2 a2, CA3 a3, CA4 a4)
{
return (a1->*f_)(a2, a3, a4);
}
};

template<typename F, typename A1, typename A2, typename A3, typename A4> struct function_wrap_4<F, A1, A2, A3, A4, reference>
{
F f_;
function_wrap_4(F f): f_(f)
{
}

template<typename CA1, typename CA2, typename CA3, typename CA4>
typename result_of<F>::type operator()(CA1 a1, CA2 a2, CA3 a3, CA4 a4)
{
return (a1.*f_)(a2, a3, a4);
}
};

template<typename F, typename A1, typename A2, typename A3, typename A4> struct function_wrap_4<F, A1, A2, A3, A4, dm_reference>
{
F f_;
function_wrap_4(F f): f_(f)
{
}

template<typename CA1, typename CA2, typename CA3, typename CA4>
typename result_of<F>::type operator()(CA1 a1, CA2 a2, CA3 a3, CA4 a4)
{
return a1.*f_;
}
};

template<typename F, typename A1, typename A2, typename A3, typename A4> struct function_wrap_4<F, A1, A2, A3, A4, dm_pointer>
{
F f_;
function_wrap_4(F f): f_(f)
{
}

template<typename CA1, typename CA2, typename CA3, typename CA4>
typename result_of<F>::type operator()(CA1 a1, CA2 a2, CA3 a3, CA4 a4)
{
return a1->*f_;
}
};
На этом этапе у нас есть уже почти нормальная реализация, которая не допускает только одного важного применения - каскадного связывания bind вида
bind(std::plus<int>(), bind(&f1, _1), _2)(2, 4);


Продолжение следует...

Комментариев нет: