次の再帰的なラムダ呼び出しはどのように終了/終了しますか?
#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;
}
ここで何が欠けていますか?
これは再帰的な関数呼び出しではありません。順を追って見てください。
terminal(stdout)
-これは単にstdout
をキャプチャしたラムダを返しますhello
で呼び出され、ラムダ(func(term)
)を実行します。その結果はterminal()
に渡され、単純にaを返します。 1のようにラムダ。world
で呼び出されます。これは、2と同じことを行いますが、今回は戻り値が破棄されます...呼び出し自体は再帰的ではありません。関数オブジェクトが返され、呼び出された場合は再度terminal
が呼び出され、さらに別の関数オブジェクトが生成されます。
そのため、terminal(stdout)
はstdout
をキャプチャし、別の関数オブジェクトで呼び出すことができるファンクタを返します。再度呼び出す(hello)
は、キャプチャされた項hello
を使用してstdout
関数を呼び出し、"Hello"
を出力します。呼び出しはterminal
を呼び出し、今度はhello
の戻り値をキャプチャする別のファンクタを返します-これはまだstdout
です。そのファンクター(world)
を呼び出して、もう一度同じように"World"
を出力します。
ここで重要なのは、これが有効であることを理解することです。
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;
}
内部的には、次のようなものに変換できます。
#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);
}
実際、これはコンパイラがラムダを使用して背後で行うことです(ある程度の近似を使用)。
混乱の原因は、ラムダ宣言をラムダ呼び出しとして読み取ることにあると思います。確かにここに:
_auto terminal = [](auto term) // <---------+
{ // |
return [=] (auto func) // | ???
{ // |
return terminal(func(term)); // >---------+
};
};
_
著者はラムダterminal
を宣言しました。これは、任意の引数term
を1つ取り、名前のないラムダを返します。名前のないこのラムダを見てみましょう。
func
を引数として受け入れ、コピーキャプチャされたパラメータterm
で呼び出します。func(term)
;そのため、func(term)
の結果を取得する名前のない別のラムダを返しますが、このラムダは今では呼び出されず、再帰はありません。これで、メインのトリックがより明確になるはずです。
terminal(stdout)
は、標準出力をキャプチャした名前のないラムダを返します。(hello)
_は、名前のないラムダを呼び出し、hello呼び出し可能オブジェクトを引数として渡します。これは、以前にキャプチャされたstdoutで呼び出されます。 hello(stdout)
は、端末への呼び出しの引数として使用されるstdoutを再び返し、stdoutをキャプチャした別の名前のないラムダを返します。(world)
_ 2と同じ。terminal(stdout)は関数を返します。paramx
を付けて関数func
と呼びましょう。そう:
terminal(stdout) ==> x(func) { return terminal(func(stdout)) };
ここで、terminal(stdout)(hello)が関数x(hello)
を呼び出します。
terminal(stdout)(hello) ==> x(hello) { return terminal(hello(stdout)) };
これにより、hello
関数が呼び出され、関数x
が再び返されます。
ここで、terminal(std)(hello)(world)が関数x(world)
を呼び出します。
terminal(stdout)(hello) ==> x(world) { return terminal(world(stdout)) };
これにより、world
関数が呼び出され、関数x
が再び返されます。関数x
は、パラメーターがなくなったため、呼び出されなくなりました。