yohhoyの日記

技術的メモをしていきたい日記

自己再帰するラムダ式 @ C++23

次期C++2b(C++23)言語仕様に追加される Deducing this により、自己再帰するラムダ式を自然に記述できるようになる。

ラムダ式の第1引数型this autoで宣言されるselfは explicit object parameter と呼ばれ、ここではラムダ式自身のクロージャ型(closure type)へ推論される。パラメータ名は任意だが、PythonやRustなど他言語の流儀にならった方が無難。

// C++2b(P0847R7より引用)
auto fact = [](this auto self, int n) -> int {
  return (n <= 1) ? 1 : n * self(n-1);
};
std::cout << fact(5);  // "120"

C++14/17/20

C++14/17/20ではジェネリックラムダ式を利用してラムダ式の自己再帰を実装する*1。呼び出し時にはクロージャオブジェクト自身を追加で指定(下記コードでは第1引数)する必要がある。

auto fact = [](auto self, int n) -> int {
  return (n <= 1) ? 1 : n * self(self, n-1);
};
std::cout << fact(fact, 5);  // "120"

不動点コンビネータfix*2を利用すると、ラムダ式への追加の引数指定が不要になる。そこまでして実装したいかはともかく。

template<typename F>
struct fix {
  F f;
  template<typename... Args>
  decltype(auto) operator()(Args&&... args) const&
    { return f(std::ref(*this), std::forward<Args>(args)...); }
};
// テンプレート推論ガイド(deduction guide)
template<typename F> fix(F) -> fix<F>;  // C++20以降は明示不要

auto fact = fix{[](auto self, int n) -> int {
  return (n <= 1) ? 1 : n * self(n-1);
}};
std::cout << fact(5);  // "120"

// C++14では型推論のためヘルパ関数 make_fix を経由する
// template<typename F> fix<F> make_fix(F f) { return {f}; }
// auto fact = make_fix([](auto self, int n) -> int { ... });

C++11

ラムダ式が導入されたC++11時点ではstd::function利用しか実現手段がない。動的メモリ確保(→id:yohhoy:20201111)や型消去(type erasure)による実行時オーバーヘッドが生じるため、実用性のほどは微妙か。素直に関数定義したほうがマシ。

#include <functional>

std::function<int(int)> fact = [&](int n) -> int {
  return (n <= 1) ? 1 : n * fact(n-1);
};
std::cout << fact(5);  // "120"

関連URL