Здравствуйте, Шахтер, Вы писали:
Ш>Создание многоаргументных функций.
Возможны варианты:
#include <iostream>
using namespace std;
// уникально именованая параtemplate<class F, class A> struct and_more { F f; A a; };
template<class F, class A> auto make_and_more(F f, A a) { return and_more<F,A>{f, a}; }
template<class X, class F, class Y>
static auto operator > (X x, and_more<F,Y> fy) { return fy.f(x, fy.a); }
template<class F, class X, class... Ys>
auto fold_left(F f, X x, Ys... ys) { return (x > ... > make_and_more(f, ys)); }
template<class X, class F, class Y>
static auto operator < (and_more<F,X> fx, Y y) { return make_and_more(fx.f, fx.f(fx.a, y)); }
template<class F, class X, class... Ys>
auto fold_left_v2(F f, X x, Ys... ys) { return (make_and_more(f, x) < ... < ys).a; }
// тестируем!
// полиморфная функцияstruct verbose_second {
template<class X, class Y>
auto operator()(X x, Y y) const {
cout << __PRETTY_FUNCTION__ << "(" << x << "," << y << ") = " << y << endl;
return y;
}
};
struct verbose_plus {
template<class X, class Y>
auto operator()(X x, Y y) const {
cout << __PRETTY_FUNCTION__ << "(" << x << "," << y << ") = " << y << endl;
return x + y;
}
};
int main() {
auto z = fold_left(verbose_second(), '1', 2, 3u, "4?!");
cout << z << endl;
auto z2 = fold_left_v2(verbose_second(), '1', 2, 3u, "4?!");
cout << z2 << endl;
auto t = fold_left(verbose_plus(), char(1), char(2), 3U, 4L, 5UL, 6.f, 7., 8);
cout << t << endl;
}
fold_left сразу создаёт все пары (функция,операнд), затем сворачивает их.
fold_left_v2 — создаёт пары (функция,результат).
Что лучше, это ещё вопрос. По идее, вся эта куча пар должна дожить до конца полного выражения.
И я бы предпочёл именно первый вариант, т.к. он позволяет держать ссылки на операнды, тогда как второй безусловно должен держать значения.
Кстати, вопрос. Как бесплатно сделать fold_right?
Замена на правоассоциативный оператор ничего не даёт: коварный вариадик всё равно разворачивается левоассоциативно.
Здравствуйте, Кодт, Вы писали:
К>Кстати, вопрос. Как бесплатно сделать fold_right? К>Замена на правоассоциативный оператор ничего не даёт: коварный вариадик всё равно разворачивается левоассоциативно.
Всё просто.
( ... + AA ) -- ( a + b ) + c
( AA + ... ) -- a + ( b + c )
Здравствуйте, Шахтер, Вы писали:
Ш>Всё просто.
Ш>( ... + AA ) -- ( a + b ) + c Ш>( AA + ... ) -- a + ( b + c )
Ш>От оператора не зависит.
А для пустого списка что получится?
С foldl всё просто, вводим стартовое значение
(x0 + ... + ys) = (((x0+y1) + y2) + y3)
С foldr — мы не сможем сматчить аргументы: вариадик должен быть последним.
template<class F, class... Xs, class Y>
auto foldr(F f, Xs... xs, Y y) // объявить-то можем, а использовать - нет.
{
return (xs + ... + operand(f,y));
}
Выход, разве что, в создании нейтрального элемента — и перегрузки оператора для него.
struct N {};
template<class F, class A> struct operand { F f; A a; };
template<class F, class A> auto make_operand(F f, A a) { return operand<F,A>{f,a}; }
template<class F, class A, class Y> auto operator < (operand<F,A> x, Y y) { return make_operand(x.f, x.f(x.a, y)); }
template<class X, class F, class A> auto operator > (X x, operand<F,A> y) { return make_operand(y.f, y.f(x, y.a)); }
template<class X, class F> auto operator > (X x, operand<F,N> y) { return make_operand(y.f, x); }
template<class F, class X, class... Xs> auto foldl(F f, X x, Xs... xs) { return (make_operand(f,x) < ... < xs).a; }
template<class F, class X, class... Xs> auto foldr(F f, X x, Xs... xs) { return (x > (xs > ... > make_operand(f,N()))).a; }
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, Шахтер, Вы писали:
Ш>>Всё просто.
Ш>>( ... + AA ) -- ( a + b ) + c Ш>>( AA + ... ) -- a + ( b + c )
Ш>>От оператора не зависит.
К>А для пустого списка что получится?
В этих двух вариантах список должен быть непустой.
Здравствуйте, Шахтер, Вы писали:
К>>А для пустого списка что получится? Ш>В этих двух вариантах список должен быть непустой.
Понятно, что отчасти компилятор нам сам делает подарок. Хотим свёртку без выделенного начального значения, компилятор побеспокится, чтобы взять его из списка, и потребует, чтоб тот был непустым. Никаких лишних проверок с нашей стороны.
Но тут есть два минуса.
Первый: проверка валидности проваливается внутрь функции. Вместо ошибки сопоставления получаем ошибку компиляции. Хотим управлять сопоставлением — должны прикладывать нетривиальные усилия, старое доброе sfinae или перегрузки руками расписывать.
Для левой свёртки таких проблем нет: foldl(f,x0,xs...)
Для правой — либо пишем навыворот: foldr(f,xn,xs...), либо что-то типа foldr(f,xs...)->enable_if(sizeof...(xs)>0)
Второй: принудительно однородная свёртка. Аккумулятор вынужден быть того же вида, что и все остальные операнды.
Применительно к нашему трюку, когда мы операнды склеиваем с функцией, — для неоднородной свёртки надо сделать N-1 проекций, по числу операторов, а для однородной — N, то есть, один раз лишний.
Хотя, впрочем, отделять аккумулятор от списка — это правильный подход, Шейнфинкель с Карри одобрили бы: foldl(f,x0)(xs...) и foldr(f,xn)(xs...)
namespace aux {
template<class F, class X> struct seed_t { F f; X x; };
template<class F, class X> auto lift(F f, X x) { return seed_t<F,X>{f,x}; }
template<class F, class X> auto land(seed_t<F,X> fx) { return fx.x; }
template<class F, class X, class A, class B> auto play(seed_t<F,X> fx, A a, B b) { return lift(fx.f, fx.f(a,b)); }
template<class Y, class F, class X> auto operator + (Y y, seed_t<F,X> fx) { return play(fx, y, land(fx)); }
template<class Y, class F, class X> auto operator + (seed_t<F,X> fx, Y y) { return play(fx, land(fx), y); }
}
template<class F, class X> struct fold_t {
F f; X x;
template<class... Ys> auto left (Ys... ys) const { return aux::land((aux::lift(f,x) + ... + ys)); }
template<class... Ys> auto right(Ys... ys) const { return aux::land((ys + ... + aux::lift(f,x))); }
};
template<class F, class X> auto fold(F f, X x) { return fold_t<F,X>{f,x}; }
int main() {
cout << fold( [](auto x, auto y) { return x*10 + y; }, 0 ).left (1, 2, 3, 4) << endl; // 0*10+1, 1*10+2, 12*10+3, 123*10+4 = 1234
cout << fold( [](auto x, auto y) { return x + y*10; }, 0 ).right(1, 2, 3, 4) << endl; // 4+0*10, 3+4*10, 2+43*10, 1+432*10 = 4321
}
Ну, тут можно подискутировать, что более рябит, а что менее: хакнуть двухместный оператор (поиграть в аппликативные функторы) и затем воспользоваться свёрткой, или написать рекурсию вручную.
template<class F, class X>
auto foldl(F f, X x) { return x; }
template<class F, class X, class Y, class... Zs>
auto foldl(F f, X x, Y y, Zs... zs) { return foldl(f, f(x,y), zs...); }
////////template<class F, class X>
auto foldr(F f, X x) { return x; }
template<class F, class X, class... Zs>
auto foldr(F f, X x, Zs... zs) { return f(x, foldr(f, zs...)); }
Здравствуйте, uzhas, Вы писали:
U>Здравствуйте, Кодт, Вы писали:
К>>Ну, тут можно подискутировать, что более рябит
U>в первую очередь рябит вырвиглазный стиль ТС
U>в первую очередь рябит вырвиглазный стиль ТС
"Верблюжий" регистр/стиль. В целом ничего плохого. Хуже когда несколько стилей мешают в кучу.
По теме, мне так не совсем понятно с практической точки зрения что с этим делать?
Ну и из-за поддержки всех этих "костылей" потом в продакшене, я не люблю применение шаблонов без необходимости.