実は、std::vector
はC++17から不完全型をサポートしたので、std::variant
と組み合わせると以下のようなコードが書ける。
struct config_t { std::variant<bool, int, double, std::string, std::vector<config_t>> data; };
これを上手く使えばJSONなどの木構造をかなり簡単に(ポインタを陽に触らずに)扱える。std::map
が不完全型をサポートしてくれていないのがつらいが、std::vector<std::pair<std::string, config_t>>
とすることで強引に回避できる。
これをしたかったらしき人の質問を見つけた。
これが通らないとのことだ。
class ScriptParameter; using ScriptParameter = std::variant<bool, int, double, std::string, std::vector<ScriptParameter> >;
Best answerの人のいう通り、これは最初にclass
としてScriptParameter
を設定しているから2重定義になるのであって、クラスの中に入れておけばよい。この回答のコードに関しては、継承することを想定していないクラスを継承するのはいつも少し不安になるので私はいつも所有関係にするが、まあ今回の主題ではない。
ここに異彩を放つ回答がついており、「不動点コンビネータを使え」と一括していた。TaPL輪読会真っ只中(最近再帰型を担当して発表した)なので不動点コンビネータとはそれなりに頻繁に触れ合っておりそこそこ身近に感じてはいたが、まさかこんなところでこれを使うコードが出てくると思っていなかったので見たときちょっと笑ってしまった。
この回答だ。https://stackoverflow.com/a/53504373
// non-recursive definition template<class T> using Var = std::variant<int, bool, double, std::string, std::vector<T>>; // tie the knot template <template<class> class K> struct Fix : K<Fix<K>> { using K<Fix>::K; }; using ScriptParameter = Fix<Var>;
しかも動くし。
しかもこれは継承を使っているので、std::get
などの参照をとる関数にScriptParameter
を直接渡せるし、メンバ関数はそのまま呼び出せる。面白いことを考える人がいるものだ。