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>
のような再帰的に探索して値を持ってくるものもできるんですかね?
試してみたいです