examples/lazy-config/ in the source tree. Policy: single_threaded.
A great many programs read a small config file once at startup and then consult it repeatedly. Wrapping the parse in a memoized_invoke makes "once" literal, gives you a reset() for a SIGHUP-style reload, and keeps the parsed map owned in one place.
The program
#include <memoized_invoke.hh>
#include <fstream>
#include <iostream>
#include <map>
#include <string>
using fedem::utility::memoized_invoke;
namespace
{
using config_map = std::map< std::string, std::string >;
std::string trim( std::string s )
{
auto const b = s.find_first_not_of( " \t\r" );
auto const e = s.find_last_not_of( " \t\r" );
return b == std::string::npos ? std::string{} : s.substr( b, e - b + 1 );
}
config_map parse_config( std::string const& path )
{
// The one visible side effect — it must appear exactly once per load.
std::cout << "[parse] reading config file\n";
config_map out;
std::ifstream in( path );
std::string line;
while( std::getline( in, line ) )
{
if( auto const hash = line.find( '#' ); hash != std::string::npos )
line.erase( hash );
auto const eq = line.find( '=' );
if( eq == std::string::npos )
continue;
auto const key = trim( line.substr( 0, eq ) );
auto const val = trim( line.substr( eq + 1 ) );
if( ! key.empty( ) )
out[ key ] = val;
}
return out;
}
} // namespace
int main( int argc, char** argv )
{
if( argc != 2 )
{
std::cerr << "usage: lazy-config <file>\n";
return 1;
}
std::string const path = argv[ 1 ];
memoized_invoke config( &parse_config, path );
std::cout << std::boolalpha
<< "before first call, is_done = " << config.is_done( ) << '\n';
for( int pass = 1; pass <= 3; ++pass )
{
config_map const& cfg = config( );
std::cout << "pass " << pass
<< ": host=" << cfg.at( "host" )
<< " port=" << cfg.at( "port" )
<< " (is_done = " << config.is_done( ) << ")\n";
}
config.reset( );
std::cout << "after reset, is_done = " << config.is_done( ) << '\n';
config_map const& reloaded = config( );
std::cout << "reloaded retries=" << reloaded.at( "retries" ) << '\n';
return 0;
}
Reading it
memoized_invoke config( &parse_config, path ); — CTAD deduces memoized_invoke<single_threaded, config_map(*)(std::string const&), std::string>. The path is stored (decayed to std::string); nothing has run yet, so is_done() is false.
The three-pass loop. config() on the first pass calls std::apply(parse_config, {path}), which prints [parse] reading config file, builds the map, and caches it. Passes two and three return the cached map — [parse] does not print again. The whole point.
config_map const& cfg = config(); — operator() returns the result_type (a config_map) by value. Binding it to a const& extends the temporary's lifetime to the reference's scope. If the map were large and this were a hot path you would call config.value() instead, which returns a reference into the object's own storage with no copy — but it has the precondition is_done() == true.
config.reset(); — clears the cached map and returns to not-started. The stored path is kept. The next config() re-reads the file — you can see the second [parse] line. This is the line you would call from a signal handler.
Trying it
The example's smoke test (examples/lazy-config/src/CMakeLists.txt) runs it against tests/fixture.conf:
# lazy-config demo — a tiny key = value file
host = db.internal
port = 5432
retries = 3 # trailing comments are stripped
and checks the transcript byte-for-byte:
before first call, is_done = false
[parse] reading config file
pass 1: host=db.internal port=5432 (is_done = true)
pass 2: host=db.internal port=5432 (is_done = true)
pass 3: host=db.internal port=5432 (is_done = true)
after reset, is_done = false
[parse] reading config file
reloaded retries=3
A second test runs it with no argument and pins that it prints a usage line and exits 1 rather than crashing on std::string(argv[1] == nullptr).
See also
- Argument-sensitive cache — the
operator()(args...)overload. - Reference › memoized_invoke::reset.
- User Guide › Getting Started.

