web-dev-qa-db-ja.com

C ++ 14のLambda-Over-Lambda

次の再帰的なラムダ呼び出しはどのように終了/終了しますか?

#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

69
P0W

これは再帰的な関数呼び出しではありません。順を追って見てください。

  1. terminal(stdout)-これは単にstdoutをキャプチャしたラムダを返します
  2. 1.の結果はラムダhelloで呼び出され、ラムダ(func(term))を実行します。その結果はterminal()に渡され、単純にaを返します。 1のようにラムダ。
  3. 2.の結果は、ラムダworldで呼び出されます。これは、2と同じことを行いますが、今回は戻り値が破棄されます...
45
Nim

呼び出し自体は再帰的ではありません。関数オブジェクトが返され、呼び出された場合は再度terminalが呼び出され、さらに別の関数オブジェクトが生成されます。

そのため、terminal(stdout)stdoutをキャプチャし、別の関数オブジェクトで呼び出すことができるファンクタを返します。再度呼び出す(hello)は、キャプチャされた項helloを使用してstdout関数を呼び出し、"Hello"を出力します。呼び出しはterminalを呼び出し、今度はhelloの戻り値をキャプチャする別のファンクタを返します-これはまだstdoutです。そのファンクター(world)を呼び出して、もう一度同じように"World"を出力します。

26
Mike Seymour

ここで重要なのは、これが有効であることを理解することです。

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;

}

コリルの例

13
Marco A.

内部的には、次のようなものに変換できます。

#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

実際、これはコンパイラがラムダを使用して背後で行うことです(ある程度の近似を使用)。

10
Piotr Skotnicki

混乱の原因は、ラムダ宣言をラムダ呼び出しとして読み取ることにあると思います。確かにここに:

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

著者はラムダterminalを宣言しました。これは、任意の引数termを1つ取り、名前のないラムダを返します。名前のないこのラムダを見てみましょう。

  • 呼び出し可能なオブジェクトfuncを引数として受け入れ、コピーキャプチャされたパラメータtermで呼び出します。
  • 呼び出しの結果で呼び出された端末の結果を返しますfunc(term);そのため、func(term)の結果を取得する名前のない別のラムダを返しますが、このラムダは今では呼び出されず、再帰はありません。

これで、メインのトリックがより明確になるはずです。

  1. terminal(stdout)は、標準出力をキャプチャした名前のないラムダを返します。
  2. _(hello)_は、名前のないラムダを呼び出し、hello呼び出し可能オブジェクトを引数として渡します。これは、以前にキャプチャされたstdoutで呼び出されます。 hello(stdout)は、端末への呼び出しの引数として使用されるstdoutを再び返し、stdoutをキャプチャした別の名前のないラムダを返します。
  3. _(world)_ 2と同じ。
8
DarioP
  1. terminal(stdout)は関数を返します。paramxを付けて関数funcと呼びましょう。そう:

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

  2. ここで、terminal(stdout)(hello)が関数x(hello)を呼び出します。

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

    これにより、hello関数が呼び出され、関数xが再び返されます。

  3. ここで、terminal(std)(hello)(world)が関数x(world)を呼び出します。

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

    これにより、world関数が呼び出され、関数xが再び返されます。関数xは、パラメーターがなくなったため、呼び出されなくなりました。

3
Krypton