Defined in header <memoized_invoke.hh>
explicit memoized_invoke( F func ) // (1)
requires ( sizeof...( Args ) == 0 );
explicit memoized_invoke( F func, std::decay_t< Args >... args ) // (2)
requires ( sizeof...( Args ) > 0 );
memoized_invoke( memoized_invoke const& other ) // (3)
requires std::is_copy_constructible_v< F >;
memoized_invoke( memoized_invoke&& other ) noexcept(/* see Notes */); // (4)
Parameters
| Parameter | Description |
|---|---|
func | The callable. Moved into the object. |
args | The argument set to invoke func with. Taken by value (already decayed) and moved into the stored tuple. |
other | Another memoized_invoke of the same type to copy (3) or move (4) from. |
1) Nullary constructor
Participates in overload resolution only when sizeof...(Args) == 0. Constructs a wrapper over a callable that takes no arguments. The stored argument tuple is empty.
memoized_invoke a( []{ return 42; } );
memoized_invoke b( &initialise_logging ); // void()
2) Constructor with arguments
Participates only when sizeof...(Args) > 0. Stores args... by value as std::tuple<std::decay_t<Args>...>.
memoized_invoke c( &std::pow, 2.0, 10.0 );
memoized_invoke d( &Widget::resize, &widget, 800, 600 ); // object ptr is arg 0
3) Copy constructor
Copies the callable, the policy, the cached value and the arguments. Requires std::is_copy_constructible_v<F>.
- With
single_threadedthe policy state is copied, so a copy of a done object is itself done and serves the cached value without re-running. - With
lock_freethe policy state is re-initialised to *not-started* —std::atomicis not copyable and a copy owns a distinct synchronisation context. The cached value is still copied, butis_done()on the copy isfalseuntil its first call.
4) Move constructor
Moves the callable, policy, cached value and arguments. Same lock_free state re-initialisation as the copy. noexcept when both std::is_nothrow_move_constructible_v<F> and std::is_nothrow_move_constructible_v<std::tuple<std::decay_t<Args>...>> hold.
<a id="assignment"></a>Assignment
memoized_invoke& operator=( memoized_invoke const& other )
requires std::is_copy_assignable_v< F >;
memoized_invoke& operator=( memoized_invoke&& other ) noexcept(/* as (4) */);
Both are self-assignment safe (this != &other guard). Copy assignment requires std::is_copy_assignable_v<F>. The same policy-state rules as the constructors apply: lock_free resets, single_threaded copies/moves the state.
<a id="destructor"></a>Destructor
~memoized_invoke() = default;
Trivial in effect — the object owns only value members (the callable, the policy, an optional, a tuple). No thread is joined, no lock is released; destroying a memoized_invoke while another thread is inside its operator() is a data race, the same as for any other object.
Notes
There is no default constructor: a memoized_invoke is meaningless without a callable.
CTAD (deduction guides) makes the class-name form work without template arguments and always picks single_threaded. For lock_free, use make_memoized.
Example
#include <memoized_invoke.hh>
#include <cassert>
using namespace fedem::utility;
int main()
{
int runs = 0;
memoized_invoke src( [&runs]{ return ++runs; } );
assert( src() == 1 );
auto copy = src; // single_threaded: state copied
assert( copy.is_done() );
assert( copy() == 1 ); // no re-run
assert( runs == 1 );
auto shared = make_memoized<lock_free>( [&runs]{ return ++runs; } );
shared(); // runs -> 2
auto shared_copy = shared; // lock_free: state reset
assert( !shared_copy.is_done() );
shared_copy(); // runs -> 3
}
See also
- operator() — invoke / read the cache.
- lock_free — why copy/move reset the state.
- make_memoized — construct with a chosen policy.

