2014-09-02 21 views
70

Jak kończy się/kończy się rekurencyjne wywołanie lambda?Lambda-Over-Lambda w C++ 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; 

} 

Czego tu brakuje?

Running code

+24

Nice one na tej liście: http://www.gnu.org/fun/jokes/helloworld.html – stefaanv

+0

Faktycznie, jestem ciekaw, jak ten (jest/może być) o nazwie, więc wysłałem kolejne pytanie: http://stackoverflow.com/questions/25619769/how-is-this-c14-construct-called – stefaanv

+1

Duplikat [tego pytania] (http://stackoverflow.com/q/25338795/596781)? –

Odpowiedz

46

To nie jest rekurencyjne wywołanie funkcji, spójrz na niego krok po kroku:

  1. terminal(stdout) - to po prostu zwraca lambda, które zostały przechwycone stdout
  2. Wynik 1. nazywa lambda hello, który wykonuje lambda (func(term)), którego wynik jest przekazywany do terminal(), który po prostu powraca do lambda w 1.
  3. wynikiem 2. nazywa lambda world, który działa tak samo jak 2, tym razem wartość zwracana y wyrzucić ...
26

Sama rozmowa nie jest rekurencyjny. Zwraca obiekt funkcji, który, jeśli zostanie wywołany, ponownie wywoła terminal, aby wygenerować kolejny obiekt funkcji.

Tak więc terminal(stdout) zwraca funktor, który przechwytuje stdout i może być wywołany z innym obiektem funkcji. Wywołanie go ponownie, (hello), wywołuje funktor hello z przechwyconym określeniem stdout, wyprowadzając "Hello"; wywołania terminal i zwraca inny funktor, który tym razem przechwytuje wartość zwracaną hello - która nadal jest stdout. Wywołanie tego funktora, (world), znowu to samo, wyprowadzanie "World".

+0

Dzięki, że przechodziłem przez artykuł, w którym autor używał dwóch różnych lambda, jednak nieświadomie próbowałem owijać go nad oryginałem. Okazało się, że jest nieco mylące. Dzięki za wyjaśnienie. – P0W

10

To może być wewnętrznie tłumaczone na coś, co wygląda następująco:

#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

Właściwie to, co robi kompilator za sceną z lambda (z pewnym przybliżeniem).

+1

Prawda (i technicznie imponująca), ale nieistotna dla pytania zadawanego przez PO. – Quuxplusone

13

Kluczem tutaj jest, aby zrozumieć, że to jest ważne:

world(hello(stdout)); 

i drukuje "Hello World". Rekurencyjne seria lambdas może być rozwijana jako

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

8

myślę, że źródłem zamieszania pochodzi z czytania deklaracji lambda jako wezwanie lambda. Rzeczywiście tutaj:

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

autor właśnie ogłoszony lambda terminal która trwa jeden dowolnego argumentu term i zwraca nienazwany lambda, nic więcej!Spójrzmy na tej nienazwanej lambda, to:

  • akceptuje wpłacone obiekt func jako argument i zwraca go w parametrze kopiowaniem schwytany term i
  • zwraca wynik terminalu zwanego z wyniku wezwania func(term); więc zwraca inną nienazwaną lambdę, która przechwytuje wynik func(term), ale ta lambda nie jest już wywołana, nie ma rekursji.

Teraz trick w głównym powinien być bardziej jasne:

  1. terminal(stdout) zwraca nienazwany lambda, które zostały przechwycone stdout.
  2. (hello) nazywa tę bezimienną lambdę przechodzącą jako arg na cześć. To zostanie wywołane na poprzednio przechwyconym wyjściu standardowym. hello(stdout) zwraca ponownie stdout, który jest używany jako argument wywołania na terminalu, zwracający inną nienazwaną wartość lambda, która przechwyciła standardowe wyjście.
  3. (world) same jak 2.
3
  1. zacisk (wyjścia) wraca do funkcji, niech nazwać funkcja x z param func. Więc:

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

  2. Teraz zacisk (stdout) (cześć) wywołuje funkcję x(hello):

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

    Powoduje hello funkcji sprawdzony i wraca funkcjonować x ponownie.

  3. Teraz zacisk (STD) (cześć) (świat) wywołuje funkcję x(world):

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

    Powoduje world funkcji sprawdzony i wraca funkcjonować x ponownie. Funkcja x teraz nie jest już wywoływana, ponieważ nie ma więcej parametrów.

Powiązane problemy