Let the code speak for itself first with naive approach:
int heavy_calc() // needed to be called once
{
// sleep(7500000 years)
return 42;
}
int main()
{
auto foo = [] {
// And cached for lambda return value
static int cache = heavy_calc();
return cache;
};
return foo() + foo();
}
I want to have lambda internal cached value calculated on the first call. An naive approach is to use static
cache, but it increases binary size and refuses to be be inlined.
I came up with creating cache
in capture list and marking lambda as mutable
, what inlines without problems, but requires cache to start with default value, which may break class invariant.
auto foo = [cache=0] () mutable {
// And cached for lambda return value
if(!cache)
cache = heavy_calc();
return cache;
};
My third approach uses boost::optional
in mutable lambda
auto foo = [cache=std::optional<int>{}] () mutable {
// And cached for lambda return value
if(!cache)
cache = heavy_calc();
return *cache;
};
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.
Maybe there is an better/more clean solution for this? Or just different approach which ends up with the very same effect.
EDIT, some background:
Lambda approach is chosen as I am modifying some callback lambda, which currently is used as:
[this, param]{this->onEvent(heavy_calc(param));}
I want to reduce heavy_calc
calls without evaluating it in advance (only on first call)
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With