воскресенье, 23 мая 2010 г.

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

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


Теперь займемся каскадным связыванием...
Понятно, что для нормального связывания нужно изменить определение placeholder_4.
Вот только как?
Здесь я уже начну слегка халявить - мы произведем bind_obj_4 от общего базового класса bind_obj_base и научимся распознавать классы-наследники с помощью еще одного шаблона:
template<class D, class B>
struct is_derived_from
{
typedef char (&no) [1];
typedef char (&yes) [2];

static no probe(...);
static yes probe(B*);

enum {result = sizeof(probe((D*)0) ) == sizeof(yes) };
};
Полученный шаблон очень напоминает то, что мы уже делали раньше - мы используем факт, что аргумент(...) при подборе компилятором "наилучших" параметров будет рассматриваться последним и проверяем тип результата возвращаемого значениея на совпадение размера с правильным ответом.

Определим на основе него две специализации placeholder_4, одна уже нам известная, а вторую мы сейчас создадим.
template<typename A, bool d = is_derived_from<A, bind_obj_base>::result> struct placeholder_4;

template<typename A> struct placeholder_4<A, false>
{
placeholder_4(A a): a_(a) {}

template<typename A1, typename A2, typename A3, typename A4>
typename A operator()(A1 a1, A2 a2, A3 a3, A4 a4)
{
return a_;
}

A a_;
};


Маленький спойлер - понятно, что на bind'е c четырьмя аргкментами мы не остановимся, поэтому нужно предусмотреть возможность вызывать функтор с любым количеством аргументов от 0 до 4.

Используем для вызова связанного функтора шаблонную функцию wrap_call_4, которая будет получать набор параметров вызова ровно такой же, как и основной bind.
Для облегчения работы используем еще один трюк - мы уже умеем считать количество placeholder'ов, используем его для переключения между реализациями.
Поскольку мы хотим использовать шаблонную функцию, придется обеспечить вывод параметров на этапе компиляции.
Следовательно, нужен некий тип, который будет меняться в зависимости от количества аргументов. Нет ничего проще:
template <int N>
struct int_2_type
{
enum { value = N };
};
С учетом этого wrap_call_4 будет выглядеть так:
template<typename A, typename A1, typename A2, typename A3, typename A4>
typename A::result_type wrap_call_4(A a_, A1 a1, A2 a2, A3 a3, A4 a4, int_2_type<0>)
{
return a_();
}

template<typename A, typename A1, typename A2, typename A3, typename A4>
typename A::result_type wrap_call_4(A a_, A1 a1, A2 a2, A3 a3, A4 a4, int_2_type<1>)
{
return a_(a1);
}

template<typename A, typename A1, typename A2, typename A3, typename A4>
typename A::result_type wrap_call_4(A a_, A1 a1, A2 a2, A3 a3, A4 a4, int_2_type<2>)
{
return a_(a1, a2);
}

template<typename A, typename A1, typename A2, typename A3, typename A4>
typename A::result_type wrap_call_4(A a_, A1 a1, A2 a2, A3 a3, A4 a4, int_2_type<3>)
{
return a_(a1, a2, a3);
}

template<typename A, typename A1, typename A2, typename A3, typename A4>
typename A::result_type wrap_call_4(A a_, A1 a1, A2 a2, A3 a3, A4 a4, int_2_type<4>)
{
return a_(a1, a2, a3, a4);
}


На базе этой работы напишем специализацию placeholder_4, предварительно добавив в bind_obj_4 arg::count, который уж точно известен в момент объявления экземпляра класса (и запомнить его в enum внутри класса не представляет никакой проблемы):
template<typename A> struct placeholder_4<A, true>
{
placeholder_4(A a): a_(a) {}

template<typename A1, typename A2, typename A3, typename A4>
typename A::result_type operator()(A1 a1, A2 a2, A3 a3, A4 a4)
{
return wrap_call_4(a_, a1, a2, a3, a4, int_2_type<A::arg_count>());
}

A a_;
};
Вот вроде бы с каскадным связыванием и все...

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

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