Теперь можно задуматься над расширением нашей библиотеки - количество параметров в вызываемой функции (функторе/методе класса) не обязательно равно четырем.
Чтобы решить эту проблему я предлагаю воспользоваться кодогенератором.
Причин поступить именно так, помимо моей личной лени, достаточно много:
Эти требования сразу же решают судьбу списков типов в стиле Александреску и и BOOST_PREPROCESSOR - мы хотим сделать сразу работающий вариант, который не требуется каждый раз долго и мучительно компилировать, для больших проектов такие "гениальные" модули могут замедлять сборку очень серьезно...
В качестве инструмента возьмем Perl - это все еще неплохое средство для работы с текстами, несмотря на тотальное засилие Python :-)
Немного перетасуем наши исходные коды - все разобъем условно на три части:
Каждой из частей мы сопоставим одну подпрограмму Perl (потом вторую подпрограмму мы вызовем потом столько раз, сколько нужно иметь аргументов).
Следующий шаг - выделение паттернов, которые будут общими для каждой реализации bind_obj_{n}:
Для каждого найденного паттерна организуем собственную переменную, используя конструкцию:
my $ var = map { "xxx$_" } (1..$arg_count)
Здесь используется как интерполяция переменных Perl, так и неименованная подпрограмма.Для остального вывода удобнее всего употребить ряд конструкций "document here" вида
print<<EOT;
my string1 $arg_count
my string2
EOT
Здесь также будет использована интерполяция переменных, нужно только помнить, что при возникновении неоднозначных трактовок имя переменной следует заключать в пару скобок "{}".Все деликатные внутренние моменты реализуем с помощью обычного оператора for () {}.
Да, use strict использовать обязательно, иначе отлаживаться можно до второго пришествия, а мы используем генератор чтобы экономить время...
Собственно программа укладывается в несколько строчек:
print<<EOT;
my $ARGS=9;
header $ARGS;
for (1..$ARGS) {
body $_;
}
footer $ARGS;
Добиться работоспособности генерируемого кода можно очень быстро.Обращаю внимание, body на самом деле должна вызываться, начиная с нулевого количества параметров, после чего все сразу перестает компилироваться, какое-то время уходит на адаптацию кода к отсутствию аргументов (это резко усиливает бардачность), но оно того стоит...
Эти 400 строк высоконечитабельного кода, которые дают нам нужный результат (ценителей Perl прошу смотреть в другую сторону) можно взять здесь, а полученный заголовок здесь. Полный проект MSVC2003, полный проект можно взять здесь.
Тестовый набор, который отработал (даже несколько более широкий, чем изначально изначально планировалось):
int f4(int a, int b, int c, int d)
{
return a + b + c + d;
}
int f3(int a, int b, int c)
{
return a + b + c;
}
int f2(int a, int b)
{
return a + b;
}
int f1(int a)
{
return a + 1;
}
int f0()
{
return 1;
}
struct X
{
typedef int result_type;
int f1(int a)
{
return a+1;
}
int f0()
{
return 1;
}
int f(int a1, int a2, int a3)
{
return a1 + a2 + a3;
}
};
struct S
{
std::string s;
int f()
{
}
};
int main(int argc, char* argv[])
{
X x;
using namespace mbind;
std::cout
<< bind(&f4, 1, 2, 3, _1)(4) << "\n"
<< bind(&f4, 1, 2, 3, 4)() << "\n"
<< bind(&f4, 1, _1, _2, _3)(2, 3, 4) << "\n"
<< bind(&X::f, &x, _1, _2, 3)(2, 3) << "\n"
<< bind(&f1, _1)(2) << "\n"
<< bind(&X::f1, &x, _1)(2) << "\n"
<< bind(&f3, 1, _1, _2)(2, 3) << "\n"
<< bind(&X::f0, &x)() << "\n"
<< bind(&f0)() << "\n"
<< bind(std::plus<int>(), bind(&f1, _1), _1)(2) << "\n"
<< bind(std::plus<int>(), bind(&f2, _1, 2), _1)(1) << "\n"
<< bind(std::plus<int>(), bind(&f1, _1), _2)(1, 2) << "\n"
<< bind(std::plus<int>(), bind(&f1, _1), _2)(1, 2) << std::endl;
S s;
s.s = "data";
std::cout
<< bind(&S::s, s)() << "\n"
<< bind(&S::s, _1)(s) << "\n"
<< bind(&S::s, &s)() << std::endl;
std::string s1("1"), s2("2");
std::cout << bind(std::plus<std::string>(), _1, _2)(s1, s2) << std::endl;
}
Положа руку на сердце, это не тесты, а бессистемное УГ, если бы мне на рабочем месте их предложили, я бы сказал много интересного, но тут уже силы как-то на исходе...Что можно сказать относительно полученного результата?
Единственный случай, когда их можно терпеть в коде - использование какого-нибудь олдскульного API ;-)
А так, почти ничего особо хорошего:
00404854 lea edx,[esp+110h]
0040485B mov dword ptr [ecx+18h],esi
0040485E mov dword ptr [ecx+14h],ebx
00404861 push edx
00404862 mov byte ptr [esp+13Ch],1
0040486A mov byte ptr [ecx+4],bl
0040486D call std::basic_string<char,std::char_traits<char>,std::allocator>char> >::assign (402010h)
00404872 lea eax,[esp+78h]
00404876 push eax
00404877 lea ecx,[esp+3Ch]
0040487B mov dword ptr [esp+3Ch],ebx
0040487F call mbind::bind_obj_1<std::basic_string<char,std::char_traits<char>,std::allocator<char> > S::*,mbind::arg<1>,1>::operator()<S>
(404210h)
Код с оригинальным boost выглядит несколько иначе:
004034EC push ebx
004034ED mov dword ptr [esi+18h],0Fh
004034F4 mov dword ptr [esi+14h],ebx
004034F7 push eax
004034F8 mov ecx,esi
004034FA mov byte ptr [esi+4],bl
004034FD call std::basic_string<char,std::char_traits<char>,std::allocator<char> >::assign (402010h)
00403502 cmp dword ptr [esp+24h],10h
00403507 jb std::operator+<char,std::char_traits<char>,std::allocator<char> >+86h (403516h)
00403509 mov edx,dword ptr [esp+10h]
0040350D push edx
0040350E call operator delete (406B5Dh)
00403513 add esp,4
Как только мы устраним все вопросы, объем кода устремится к оригиналу, а его понятность упадет ниже плинтуса...
Вывод прост - как игра ума это все забавно, в Production коде же желательно использовать проверенный boost::bind, или его аналог из lambda :-)
BTW, функторы в Loki тоже ведут себя не самым лучшим образом, что народ подтверждает :-)
Комментариев нет:
Отправить комментарий