C++ でメモ化を行う正しい方法は、Y コンビネータを混ぜることです。
基本関数を変更する必要があります。それ自体を直接呼び出す代わりに、それ自体へのテンプレート化された参照を最初の引数として (またはstd::function<Same_Signature>
再帰を最初の引数として) 受け取ります。
Y-combinatorから始めます。次に、 にキャッシュを追加してoperator()
名前を に変更しmemoizer
、(テーブルの) 固定署名を付けます。
tuple_hash<template<class...>class Hash>
残っている唯一のことは、タプルでハッシュを行う aを書くことです。
メモ化できる関数の型は で、型のメモ(((Args...)->R), Args...) -> R
ライザーになり( (((Args...) -> R), Args...) -> R ) -> ((Args...) -> R)
ます。「従来の」再帰的実装を生成するために Y コンビネータを使用することも役立ちます。
メモ化された関数が呼び出し中にその引数を変更すると、メモライザーは結果を間違った場所にキャッシュすることに注意してください。
struct wrap {};
template<class Sig, class F, template<class...>class Hash=std::hash>
struct memoizer;
template<class R, class...Args, class F, template<class...>class Hash>
struct memoizer<R(Args...), F, Hash> {
using base_type = F;
private:
F base;
mutable std::unordered_map< std::tuple<std::decay_t<Args>...>, R, tuple_hash<Hash> > cache;
public:
template<class... Ts>
R operator()(Ts&&... ts) const
{
auto args = std::make_tuple(ts...);
auto it = cache.find( args );
if (it != cache.end())
return it->second;
auto&& retval = base(*this, std::forward<Ts>(ts)...);
cache.emplace( std::move(args), retval );
return decltype(retval)(retval);
}
template<class... Ts>
R operator()(Ts&&... ts)
{
auto args = std::tie(ts...);
auto it = cache.find( args );
if (it != cache.end())
return it->second;
auto&& retval = base(*this, std::forward<Ts>(ts)...);
cache.emplace( std::move(args), retval );
return decltype(retval)(retval);
}
memoizer(memoizer const&)=default;
memoizer(memoizer&&)=default;
memoizer& operator=(memoizer const&)=default;
memoizer& operator=(memoizer&&)=default;
memoizer() = delete;
template<typename L>
memoizer( wrap, L&& f ):
base( std::forward<L>(f) )
{}
};
template<class Sig, class F>
memoizer<Sig, std::decay_t<F>> memoize( F&& f ) { return {wrap{}, std::forward<F>(f)}; }
この SO postに基づいてハードコーディングされたハッシュ関数を使用したライブの例。
auto fib = memoize<size_t(size_t)>(
[](auto&& fib, size_t i)->size_t{
if (i<=1) return 1;
return fib(i-1)+fib(i-2);
}
);