Lambda-Sobre-Lambda en C++14


¿Cómo termina/termina la siguiente llamada lambda recursiva?

#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;

}

¿Qué me estoy perdiendo aquí ?

Running code

Author: P0W, 2014-09-02

6 answers

No es una llamada a una función recursiva, mírala paso a paso:

  1. terminal(stdout) - esto simplemente devuelve una lambda que ha capturado stdout
  2. El resultado de 1. se llama con la lambda hello, que ejecuta la lambda (func(term)), cuyo resultado se pasa a terminal(), que simplemente devuelve una lambda como en 1.
  3. El resultado de 2. se llama con la lambda world, que hace lo mismo que 2, esta vez se descarta el valor devuelto...
 45
Author: Nim,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-02 08:37:22

La llamada en sí no es recursiva. Devuelve un objeto de función que, si se llama, llamará de nuevo a terminal para generar otro objeto de función.

Así que terminal(stdout) devuelve un funtor que captura stdout y puede ser llamado con otro objeto de función. Llamándolo de nuevo, (hello), llama al funtor hello con el término capturado stdout, produciendo "Hello"; el llama terminal y devuelve otro funtor que esta vez captura el valor devuelto de hello - que sigue siendo stdout. Llamando a ese functor, (world), de la misma manera, de salida "World".

 26
Author: Mike Seymour,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-02 08:33:40

La clave aquí es entender que esto es válido:

world(hello(stdout));

E imprimirá "Hola Mundo". La serie recursiva de lambdas se puede desenrollar como

#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;

}

Ejemplo de Coliru

 13
Author: Marco A.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-02 08:58:46

Se puede traducir internamente en algo que se ve de la siguiente manera:

#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);
}

DEMOSTRACIÓN EN VIVO

En realidad esto es lo que hace el compilador detrás de la escena con lambdas (con cierta aproximación).

 10
Author: Piotr Skotnicki,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-08 13:32:33

Creo que la fuente de confusión proviene de leer una declaración lambda como una llamada lambda. De hecho aquí:

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

El autor acaba de declarar una lambda terminal que toma un argumento arbitrario term y devuelve una lambda sin nombre, ¡nada más! Echemos un vistazo a esta lambda sin nombre:

  • acepta un objeto invocable func como argumento y lo llama en el parámetro copy-captured term y
  • devuelve el resultado de la terminal llamada con el resultado de la llamada func(term); por lo que devuelve otra lambda sin nombre que captura el resultado de func(term), pero esta lambda no se llama por ahora, no hay recursión.

Ahora el truco en general debería ser más claro:

  1. terminal(stdout) devuelve un lambda sin nombre que ha capturado stdout.
  2. (hello) llama a este lambda passing sin nombre como arg the hello callable. Esto se llama en el stdout previamente capturado. hello(stdout) devuelve de nuevo stdout que se utiliza como argumento de una llamada a terminal, devolviendo otra lambda sin nombre que ha capturado stdout.
  3. (world) igual que 2.
 8
Author: DarioP,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-02 14:58:20
  1. Terminal(stdout) devuelve una función, llamémosla function x, con param func. Así que:

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

  2. Ahora terminal(stdout)(hola) llama a la función x(hello):

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

    Esto resulta en hello la función se llama y devuelve la función x de nuevo.

  3. Ahora terminal (std)(hola) (mundo) llama a la función x(world):

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

    Esto resulta en world función get called y devuelve la función x nuevo. Function x ahora ya no se llama ya que no hay más param.

 3
Author: Krypton,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-03 04:29:56