The reference manual describes the callonce public API in cppreference-style one-page-per-symbol format. The public surface is small, so this is a short manual: one class template with four member pages, the two execution policies, the factory, and the deduction guides.

The generated Doxygen tree is the exhaustive machine-produced reference — every signature, the class and collaboration diagrams, the include graph, the call/caller graphs. This manual is the human-written companion: same symbols, the why rather than only the what.

The library

Everything is in namespace fedem::utility, declared in one header:

#include <memoized_invoke.hh>

No dependencies beyond <atomic>, <functional>, <optional>, <tuple>, <type_traits> and <utility>.

Symbols

SymbolKindOne-line summary
memoized_invokeclass templateOwns a callable + argument set; runs it at most once; caches the result.
single_threadedclassZero-overhead execution policy. Not thread-safe. The CTAD default.
lock_freeclassThread-safe execution policy — atomic CAS + C++20 atomic::wait.
make_memoizedfunction templateBuild a memoized_invoke with an explicit policy, deducing F and Args.
deduction guidesLet CTAD deduce every template parameter (always single_threaded).

memoized_invoke members

MemberPage
(constructors), copy, move, assignmentmemoizedinvoke::memoizedinvoke
operator() — both overloadsmemoized_invoke::operator()
reset — both overloadsmemoized_invoke::reset
is_done, valuememoizedinvoke::isdone, ::value
result_type (member type)memoized_invoke

For new readers

If you are starting from zero, read User Guide › Getting Started first — it walks the whole model in one page. Then come back here for the per-symbol detail, and see User Guide › Execution Policies for the single_threaded / lock_free decision.

Design decisions worth knowing about

Args... are explicit template parameters. The type is memoized_invoke<ExecutionPolicy, F, Args...>, not something that deduces argument types from F alone. That is what lets it accept std::bind_front results (templated operator(), no fixed signature), std::function, and member function pointers uniformly, with std::invoke_result_t<F, Args...> doing all the work.

Copy/move reset the lock_free state. std::atomic is not copyable, and a copy owns a separate synchronisation context, so lock_free's copy and move re-initialise the state to not-started. The cached value is still copied. single_threaded copies its state like any member. This surfaces on the constructor and lock_free pages.

One argument slot, no map. operator()(args...) compares against a single stored argument tuple. Switching arguments and switching back is two cache misses. A multi-key memoization table is a deliberate non-goal.