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() is false: invokes the callable as if by std::apply(f, stored_args), stores the result, transitions to done, and returns the result.
  • If is_done() is true: 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

ParameterDescription
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