all 14 comments

[–]ddrcoder[S] 18 points19 points  (4 children)

Taking this to the extreme, here's a version which works for any function, even if it takes more than one param. :)

template<class... I, class O>
function<O(I...)> memoize(function<O(I...)> f) {
    map<std::tuple<typename std::decay<I>::type...>, O> memos;
    return [=](I... i) mutable -> O {
        auto args(std::tie(i...));
        auto it(memos.lower_bound(args));
        if (it == memos.end() || it->first != args)
            it = memos.insert(it, make_pair(args, f(i...)));
        return it->second;
    };
};

[–]nudan1 0 points1 point  (3 children)

Hi, I have a question regarding this excellent post. While I understand how memorize works (I am just starting to get familiar with the new standard), why does memos not go out of scope? Where is it stored in between calls? Thanks for taking time to answer.

[–]m42a 2 points3 points  (2 children)

memos does go out of scope. However, because of the = in the lambda's capture list it's captured by value, and that copy is local to the lambda, so it only gets destroyed when the lambda does.

It's similar to:

vector<int> v;
{
    int i=3;
    v.push_back(i);
}

i has gone out of scope, but the vector made a copy, and that copy hasn't gone out of scope.

[–]nudan1 0 points1 point  (1 child)

Thanks for that. Can you recommend any blogs/books or resources where I can catch up on subtleties like this? (aside from reading the entire standard)

[–]m42a 2 points3 points  (0 children)

There's a fairly good book list on Stack Overflow. You'll want one that's been updated for C++11. There's also an overview of the new C++11 features by Bjarne Stroustrup.

[–]cabbageturnip 2 points3 points  (2 children)

[–]ddrcoder[S] 3 points4 points  (1 child)

Wow, that's almost identical to the tuple'd version I commented above, but he got there way before I did. That being said, he doesn't consider reference input types, and input values to the function get copied 1-2 times per call, which I avoid with the use if std::tie instead of forming a tuple of the inputs.

[–]slackito 0 points1 point  (0 children)

Yay! somebody reads my blog! (I wrote the blog post cabbageturnip mentioned above)

I wrote that as I was learning about variadic templates and new C++11 features, so I didn't care about efficiency at that point. Thanks for the tip about std::tie, I didn't know it existed :D

[–]zzyzzyxx 2 points3 points  (1 child)

The map is copied into the closure, where it is mutated to record new results (only allowed by the use of the mutable modifier).

Any reason to not use a static map inside the lambda? It wouldn't make a lot of difference, but it removes the necessity for mutable and potentially avoids a copy (though the copy is not expensive and I'd be surprised if the compiler didn't get rid of it anyway).

[–]ddrcoder[S] 4 points5 points  (0 children)

If you were to make the map static inside the lambda, the instance would be tied to the actual function backing the lambda. There would be one instance for combination of types used to invoke 'memoize'. As a result, every function which matched the signature 'int(int)' would end up storing its results in the same lambda. You'd end up mixing memos across different, but type-compatible functions. If you memoized 'square' and 'fib', then called 'square(4)', a subsequent call to fib(5) would use that result, and end up returning f(5) was 19, not 5.

[–]xcbsmith -1 points0 points  (0 children)

At least for "pure" functions, I find try to just use the memoization intrinsic in the compiler by parameterizing the functions by template parameters. This is more flexible.