Clean way to lazy initialize and cache internal value in lambda

To be honest, I don't see any reason to use lambda here. You can write a regular reusable class to cache calculation value. If you insist on using lambda then you can move value calculation to parameters so there will be no need to make anything mutable:

int heavy_calc() // needed to be called once
{
    // sleep(7500000 years)
    return 42;
}

int main()
{
    auto foo
    {
        [cache = heavy_calc()](void)
        {
            return cache;
        }
    };
    return foo() + foo();
}

online compiler

With a bit of template it is possible to write a class that will lazy evaluate and cache result of arbitrary calculation:

#include <boost/optional.hpp>
#include <utility>

template<typename x_Action> class
t_LazyCached final
{
    private: x_Action m_action;
    private: ::boost::optional<decltype(::std::declval<x_Action>()())> m_cache;

    public: template<typename xx_Action> explicit
    t_LazyCached(xx_Action && action): m_action{::std::forward<xx_Action>(action)}, m_cache{} {}

    public: auto const &
    operator ()(void)
    {
        if(not m_cache)
        {
            m_cache = m_action();
        }
        return m_cache.value();
    }
};

template<typename x_Action> auto
Make_LazyCached(x_Action && action)
{
    return t_LazyCached<x_Action>{::std::forward<x_Action>(action)};
}

class t_Obj
{
    public: int heavy_calc(int param) // needed to be called once
    {
        // sleep(7500000 years)
        return 42 + param;
    }
};

int main()
{
    t_Obj obj{};
    int param{3};
    auto foo{Make_LazyCached([&](void){ return obj.heavy_calc(param); })};
    return foo() + foo();
}

online compiler


It works properly, but looks for me as kind of capture list + mutable keyword hack. Also mutable affects all captured parameters, so makes lambda less safe in real use.

There is the solution to roll your own, hand-made lambda:

#include <optional>

int heavy_calc() // needed to be called once
{
    // sleep(7500000 years)
    return 42;
}


int main()
{
    struct {
        std::optional<int> cache;
        int operator()() {
            if (!cache) cache = heavy_calc();
            return *cache;
        }
    } foo;
    return foo() + foo();
}

It's inlined the same way and you don't need to rely on the capture+mutable hack.

Tags:

C++

Lambda

C++14