std::variantと不動点演算子

実は、std::vectorC++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>>とすることで強引に回避できる。

これをしたかったらしき人の質問を見つけた。

stackoverflow.com

これが通らないとのことだ。

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>;

しかも動くし。

wandbox.org

しかもこれは継承を使っているので、std::getなどの参照をとる関数にScriptParameterを直接渡せるし、メンバ関数はそのまま呼び出せる。面白いことを考える人がいるものだ。