Подтвердить что ты не робот

Лямбда-над-Лямбда в С++ 14

Как происходит завершение/завершение рекурсивного лямбда-вызова?

#include <cstdio>

auto terminal = [](auto term)            // <---------+  
{                                        //           |
    return [=] (auto func)               //           |  ???
    {                                    //           |
        return terminal(func(term));     // >---------+
    };
};


auto main() -> int
{
    auto hello =[](auto s){ fprintf(s,"Hello\n"); return s; };
    auto world =[](auto s){ fprintf(s,"World\n"); return s; };


    terminal(stdout)
            (hello)
            (world) ;

    return 0;

}

Что мне здесь не хватает?

Running code

4b9b3361

Ответ 1

Это не рекурсивный вызов функции, пошаговый взгляд на него:

  • terminal(stdout) - это просто возвращает лямбду, которая захватила stdout
  • Результат 1. вызывается с lambda hello, который выполняет lambda (func(term)), результат которого передается в terminal(), который просто возвращает лямбда, как в 1.
  • Результат 2. вызывается с lambda world, который делает то же самое, что и 2, на этот раз возвращаемое значение отбрасывается...

Ответ 2

Сам вызов не является рекурсивным. Он возвращает объект функции, который, если вызван, снова вызовет terminal, чтобы создать еще один объект функции.

Итак, terminal(stdout) возвращает функтор, который захватывает stdout и может быть вызван другим объектом функции. Вызвав его снова, (hello), вызывает функтор hello с захваченным членом stdout, выводя "Hello"; вызовы terminal и возвращает другой функтор, который на этот раз фиксирует возвращаемое значение hello - которое все еще stdout. Вызов этого функтора (world), снова и снова, выводя "World".

Ответ 3

Ключевым моментом здесь является понимание того, что это действительно:

world(hello(stdout));

и напечатает "Hello World". Рекурсивная серия лямбда может быть развернута как

#include <cstdio>

auto terminal = [](auto term)            // <---------+  
{                                        //           |
    return [=] (auto func)               //           |  ???
    {                                    //           |
        return terminal(func(term));     // >---------+
    };
};

/*
terminal(stdout) -returns> anonymous_lambda which captures stdout (functor)
anonymous_lambda(hello) is called, func(term) is hello(stdout) and prints "Hello" and returns stdout, the anonymous_lambda -returns> terminal(stdout)
(the above 2 lines start again)
terminal(stdout) is called and -returns> anonymous_lambda which captures stdout (functor)
anonymous_lambda(world) is called, func(term) is world(stdout) and prints "World" and returns stdout, the anonymous_lambda -returns> terminal(stdout)
terminal(stdout) is called and -returns> anonymous_lambda which captures stdout (functor)
nobody uses that anonymous_lambda.. end.
*/

auto main() -> int
{
    auto hello =[](auto s){ fprintf(s,"Hello\n"); return s; };
    auto world =[](auto s){ fprintf(s,"World\n"); return s; };

    world(hello(stdout));


    terminal(stdout)
            (hello)
            (world) ;

    return 0;

}

Пример Coliru

Ответ 4

Он может быть внутренне переведен в нечто, что выглядит следующим образом:

#include <cstdio>

template <typename T>
struct unnamed_lambda
{
    unnamed_lambda(T term) : captured_term(term) {}

    template <typename A>
    unnamed_lambda operator()(A func);

    T captured_term;
};

struct terminal_lambda
{
    template <typename A>
    unnamed_lambda<A> operator()(A term)
    {
        return unnamed_lambda<A>{term};
    }
};

terminal_lambda terminal;

template <typename T>
template <typename A>
unnamed_lambda<T> unnamed_lambda<T>::operator()(A func)
{
    return terminal(func(captured_term));
}

struct Hello
{
    FILE* operator()(FILE* s)
    {
        fprintf(s, "Hello\n");
        return s;
    }
};

struct World
{
    FILE* operator()(FILE* s)
    {
        fprintf(s, "World\n");
        return s;
    }
};

int main()
{    
    Hello hello;
    World world;
    unnamed_lambda<FILE*> l1 = terminal(stdout);
    unnamed_lambda<FILE*> l2 = l1(hello);
    unnamed_lambda<FILE*> l3 = l2(world);

    // same as:
    terminal(stdout)(hello)(world);
}

LIVE DEMO

На самом деле это то, что компилятор делает за сценой с lambdas (с некоторым приближением).

Ответ 5

Я думаю, что источником путаницы является чтение лямбда-декларации в виде лямбда-звонка. Действительно здесь:

auto terminal = [](auto term)            // <---------+  
{                                        //           |
    return [=] (auto func)               //           |  ???
    {                                    //           |
        return terminal(func(term));     // >---------+
    };
};

автор просто объявил lambda terminal, который принимает один произвольный аргумент term и возвращает неназванную лямбду, не более того! Давайте посмотрим на эту неназванную лямбду, она:

  • принимает вызываемый объект func в качестве аргумента и вызывает его в параметре term и
  • возвращает результат вызова терминала с результатом вызова func(term); поэтому он возвращает еще одну неназванную лямбду, которая фиксирует результат func(term), но эта лямбда не вызвана к настоящему времени, рекурсия отсутствует.

Теперь трюк в главном должен быть более ясным:

  • terminal(stdout) возвращает неназванный лямбда, который захватил stdout.
  • (hello) вызывает эту неназванную лямбду, проходящую как arg hello callable. Это вызвано на ранее записанном stdout. hello(stdout) возвращает снова stdout, который используется как аргумент вызова терминалу, возвращая еще одну неназванную лямбду, которая зафиксировала stdout.
  • (world) то же, что и 2.

Ответ 6

  • terminal (stdout) возвращает функцию, вызывая ее функцию x, с параметром func. Итак:

    terminal(stdout) ==> x(func) { return terminal(func(stdout)) };

  • Теперь терминал (stdout) (привет) вызывает функцию x(hello):

    terminal(stdout)(hello) ==> x(hello) { return terminal(hello(stdout)) };

    Это приводит к вызову функции hello get и возвращает функцию x снова.

  • Теперь терминал (std) (hello) (world) вызывает функцию x(world):

    terminal(stdout)(hello) ==> x(world) { return terminal(world(stdout)) };

    Это приводит к вызову функции world и возвращает функцию x снова. Функция x теперь уже не вызывается, поскольку больше нет параметра.