Defined in header <memoized_invoke.hh>
result_type operator()(); // (1)
result_type operator()( std::decay_t< Args >... args ) // (2)
requires ( sizeof...( Args ) > 0 );
1) Call with the stored arguments
- If
is_done()isfalse: invokes the callable as if bystd::apply(f, stored_args), stores the result, transitions to done, and returns the result. - If
is_done()istrue: returns the cached result without invoking anything. - If the callable throws: the exception propagates, the state is rolled back to not-started (see Exception safety), and the cache stays empty.
For a void result_type, there is no return value — the callable simply runs at most once.
operator()() never inspects arguments. Use it when the argument set was fixed at construction, or when the argument types are not equality-comparable.
2) Call with a fresh argument set
Available only when sizeof...(Args) > 0. Builds a tuple from args... and compares it with the stored argument tuple using operator!=:
- Arguments differ — replaces the stored arguments, clears the cache, resets the state, then proceeds as (1): the callable runs with the new arguments.
- Arguments equal — behaves exactly like (1): cached result if done, otherwise runs with the (unchanged) arguments.
memoized_invoke co( &add, 3, 4 );
co(); // 7 — executed
co(3, 4); // 7 — cached (equal)
co(10, 20); // 30 — re-executed (differ)
co(3, 4); // 7 — re-executed again (only one arg slot is stored)
Parameters
| Parameter | Description |
|---|---|
args | (overload 2) The new argument set. Taken by value as std::decay_t<Args>.... |
Return value
The cached result_type — a copy of the stored value (overload 1 and 2 both return by value). For a reference to the stored value without a copy, use value() after is_done() is true. Nothing is returned for a void result_type.
Exceptions
- Propagates anything the callable throws. On such a throw the state is restored to not-started and the cache is left empty, so the next call retries.
- Overload 2 requires the argument types to be equality-comparable; a type with no
operator==makes overload 2 ill-formed (overload 1 is unaffected).
<a id="exception-safety"></a>Exception safety
Execution is guarded by an internal RAII object. On a normal return it commits (state → done, value stored); on stack unwinding its destructor calls the policy's mark_free() (state → not-started). Under lock_free mark_free() also notify_all()s the waiters, and each woken waiter re-enters and attempts the work itself.
Notes
Overload 2's "one argument slot" behaviour is deliberate — memoized_invoke is a memoized call, not a memoization table. See User Guide › The Argument Cache.
Neither overload is thread-safe when it can mutate state: overload 2 with changing arguments, and overload 1's first run under single_threaded. Overload 1's first run under lock_free is safe to race.
Example
#include <memoized_invoke.hh>
#include <iostream>
using fedem::utility::memoized_invoke;
int main()
{
int calls = 0;
memoized_invoke sq( [&calls](int n){ ++calls; return n * n; }, 5 );
std::cout << sq() << ' '; // 25 calls == 1
std::cout << sq(5) << ' '; // 25 calls == 1 (cached)
std::cout << sq(9) << ' '; // 81 calls == 2 (re-run)
std::cout << "calls=" << calls << '\n';
}
See also
- reset — clear the cache without calling.
- is_done, value — inspect without calling.
- User Guide › The Argument Cache.

