Lambda-Over-Lambda in C++14

Hoe de volgende recursieve lambda-oproep eindigt/beëindigt?

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

Wat mis ik hier?

Running code


Antwoord 1, autoriteit 100%

Het is geen recursieve functieaanroep, bekijk het stap voor stap:

  1. terminal(stdout)– dit retourneert eenvoudig een lambda die stdout
  2. heeft vastgelegd

  3. Het resultaat van 1. wordt aangeroepen met de lambda hello, die de lambda uitvoert (func(term)), waarvan het resultaat wordt doorgegeven aan terminal(), die eenvoudig een lambda retourneert zoals in 1.
  4. Het resultaat van 2. wordt aangeroepen met de lambda world, die hetzelfde doet als 2, deze keer wordt de geretourneerde waarde weggegooid…

Antwoord 2, autoriteit 58%

De aanroep zelf is niet recursief. Het retourneert een functie-object dat, indien aangeroepen, terminalopnieuw zal aanroepen om nog een ander functie-object te genereren.

Dus terminal(stdout)retourneert een functor die stdoutvastlegt en kan worden aangeroepen met een ander functie-object. Door het opnieuw aan te roepen, (hello), wordt de hellofunctor aangeroepen met de vastgelegde term stdout, met als output "Hello"; de roept terminalaan en retourneert een andere functor die deze keer de retourwaarde van hellovastlegt – wat nog steeds stdoutis. Die functor aanroepen, (world), precies hetzelfde, met "World"als output.


Antwoord 3, autoriteit 29%

De sleutel hier is om te begrijpen dat dit geldig is:

world(hello(stdout));

en zal “Hallo wereld” afdrukken. De recursieve reeks lambda’s kan worden uitgerold als

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


Antwoord 4, autoriteit 22%

Het kan intern worden vertaald in iets dat er als volgt uitziet:

#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

Eigenlijk is dit wat de compiler achter de schermendoet met lambda’s (met enige benadering).


Antwoord 5, autoriteit 18%

Ik denk dat de bron van verwarring voortkomt uit het lezen van een lambda-aangifte als een lambda-oproep. Inderdaad hier:

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

de auteur heeft zojuist een lambda terminalgedeclareerd die één willekeurig argument termneemt en een naamloze lambda teruggeeft, meer niet! Laten we eens kijken naar deze naamloze lambda, het:

  • accepteert een oproepbaar object funcals argument en roept het aan op de door kopiëren vastgelegde parameter termen
  • retourneert het resultaat van de aangeroepen terminal met het resultaat van de aanroep func(term); dus het retourneert een andere naamloze lambda die het resultaat van func(term)vastlegt, maar deze lambda is nu niet aangeroepen, er is geen recursie.

Nu zou de truc in het algemeen duidelijker moeten zijn:

  1. terminal(stdout)retourneert een naamloze lambda die stdout heeft vastgelegd.
  2. (hello)noemt deze naamloze lambda-passing als arg de hallo callable. Dit wordt aangeroepen op de eerder vastgelegde stdout. hello(stdout)retourneert opnieuw stdout die wordt gebruikt als argument van een aanroep naar terminal, en retourneert een andere naamloze lambda die stdout heeft vastgelegd.
  3. (world)hetzelfde als 2.

Antwoord 6, autoriteit 7%

  1. terminal(stdout) retourneert een functie, laten we deze functie xnoemen, met param func. Dus:

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

  2. Nu roept terminal(stdout)(hallo) functie x(hello)aan:

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

    Hierdoor wordt de functie helloaangeroepen en wordt functie xopnieuw geretourneerd.

  3. Nu roept terminal(std)(hello)(world) functie x(world)aan:

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

    Hierdoor wordt de functie worldaangeroepen en wordt de functie xopnieuw geretourneerd. Functie xwordt nu niet meer aangeroepen omdat er geen param meer is.

Other episodes