When you write
auto unevaluted_x = []() { return foo(); };
...
auto x = unevaluted_x();
Each time you want to get the value (when you call unevaluated_x
) it's calculated, wasting computational resources. So, to get rid of this excessive work, it's a good idea to keep track whether or not the lambda has already been called (maybe in other thread, or in a very different place in the codebase). To do so, we need some wrapper around lambda:
template<typename Callable, typename Return>
class memoized_nullary {
public:
memoized_nullary(Callable f) : function(f) {}
Return operator() () {
if (calculated) {
return result;
}
calculated = true;
return result = function();
}
private:
bool calculated = false;
Return result;
Callable function;
};
Please note that this code is just an example and is not thread safe.
But instead of reinventing the wheel, you could just use std::shared_future
:
auto x = std::async(std::launch::deferred, []() { return foo(); }).share();
This requires less code to write and supports some other features (like, check whether the value has already been calculated, thread safety, etc).
There's the following text in the standard [futures.async, (3.2)]:
If launch::deferred
is set in policy, stores DECAY_COPY(std::forward<F>(f))
and DECAY_COPY(std::forward<Args>(args))...
in the shared state. These copies of f
and args
constitute
a deferred function. Invocation of the deferred function evaluates INVOKE(std::move(g), std::move(xyz))
where g
is the stored value of DECAY_COPY(std::forward<F>(f))
and xyz
is
the stored copy of DECAY_COPY(std::forward<Args>(args))....
Any return value is stored
as the result in the shared state. Any exception propagated from the execution of the deferred
function is stored as the exceptional result in the shared state. The shared state is not made
ready until the function has completed. The first call to a non-timed waiting function (30.6.4)
on an asynchronous return object referring to this shared state shall invoke the deferred function
in the thread that called the waiting function. Once evaluation of INVOKE(std::move(g),std::move(xyz))
begins, the function is no longer considered deferred. [ Note: If this policy is
specified together with other policies, such as when using a policy value of launch::async | launch::deferred
, implementations should defer invocation or the selection of the policy when
no more concurrency can be effectively exploited. —end note ]
So, you have a guarantee the calculation will not be called before it's needed.