Something simple and a bit weird:
#include <map>
#include <string>
#include <optional>
struct rmap : std::map<std::string, rmap>
{
std::optional<std::string> value; // could be anything (std::variant, std::any, ...)
};
With a bit of sugar and some other tasteful adjustments, you can use it like you intend to:
#include <map>
#include <string>
#include <optional>
#include <iostream>
struct rmap : std::map<std::string, rmap>
{
using value_type = std::optional<std::string>;
value_type value;
operator const value_type&() const { return value; }
rmap& operator=(value_type&& v) { value = v; return *this; }
friend std::ostream& operator<<(std::ostream& os, rmap& m) { return os << (m.value ? *m.value : "(nil)"); }
};
int main()
{
rmap m;
m["hello"]["world"] = std::nullopt;
m["vive"]["le"]["cassoulet"] = "Obama";
std::cout << m["does not exist"] << '\n'; // nil
std::cout << m["hello"]["world"] << '\n'; // nil
std::cout << m["vive"]["le"]["cassoulet"] << '\n'; // Obama
}
You can adjust to your taste with some syntactic sugar.