std::variant で再帰的に保持している型を辿る

なにがしたいか

using V1 = std::variant<int, std::string>;
using V2 = std::variant<double, V1>;
using V3 = std::variant<char, V2>;

int main() {
    V3 v3 = "aiueo";
    // ここで holds<std::string>(v3) == true; みたいなことがしたい
    // std::holds_alternative<std::string>(v3) ではエラーになってしまう
    // std::holds_alternative<V2>(v3)は true が返る
}

上のコードのように、std::variant がネストしているのがあると、単純な std::holds_alternative では判別するのに若干の手間がいります

こんなときに holds<std::string>(v3) みたいに判別できる関数があれば便利です

実装

ということで、実装してみたのが下のコードです

#include <bits/stdc++.h>
using namespace std;

template <typename T>
struct is_variant: std::false_type {};

template <typename... Args>
struct is_variant<std::variant<Args...>>: std::true_type {};

template <typename T>
inline constexpr bool is_variant_v = is_variant<T>::value;

template <typename T, typename Variant, size_t i = variant_size_v<Variant> - 1>
constexpr bool holds(Variant);

template <typename T, typename Variant, size_t i>
constexpr bool holds_helper(Variant a) {
  using i_th_type = variant_alternative_t<i, Variant>;
  if (not holds_alternative<i_th_type>(a)) return false;
  if constexpr (not is_variant_v<i_th_type>)
    return is_same_v<i_th_type, T>;
  else
    return holds<T, i_th_type>(get<i_th_type>(a));
}

template <typename T, typename Variant, size_t i = variant_size_v<Variant> - 1>
constexpr bool holds(Variant a) {
  static_assert(0 <= i and i < variant_size_v<Variant>);
  if constexpr (i == 0)
    return holds_helper<T, Variant, i>(a);
  else
    return holds_helper<T, Variant, i>(a) or holds<T, Variant, i - 1>(a);
}

int main() {
  V3 a = "hoge";
  cout << boolalpha;
  cout << holds<int, V3>(a) << endl;            // false
  cout << holds<string, V3>(a) << endl;       // true
  cout << holds<string>(a) << endl;             // true
}

実装の説明

やってること std::variant に入っている型を再帰的に辿って、テンプレート引数で渡された型と保持している値の型が一致していれば true を返します

holds 関数はなにをしているのか holds 関数では 0 ~ variant_size<Variant> - 1 までを探索しています そこから holds_helper 関数を呼び出して、i 番目の型が std::variant であれば探索する階層を深くして holds でまた再帰します

holds_alternative で先に return しているのは保持している値の判別と無駄な探索を防ぐのを兼ねています

holds 関数の i再帰ではなく for 文で回しても良いと思ったのですが、constexpr 関数の都合上再帰の形にしています

あとがき

これを応用すれば recurseive_get<T> のような再帰的に探索して値を持ってくるものもできるんですかね? 試してみたいです