如何迭代std::variant的类型?

8

我有一个变量 using V = std::variant<A, B, C>,以及一个原型为 V parse(const json&) 的函数。该函数应尝试解析所有类型(例如 A、B,然后 C),直到第一个成功(并且这应该是隐式的,因为时间中会有许多类型)。

如何实现这种功能?

我们可以使用 std::variant_size 来实现。

这里 有一些接近我需要的东西。

我的解决方案是显式列出所有类型的解析器。

V parse(const json& i_j)
{
using Parser = std::function<MaybeV(const json&)>;
static const auto ps = std::vector<Parser>{
  [](const auto& j)->MaybeV{return j.get<std::optional<A>>;},
  [](const auto& j)->MaybeV{return j.get<std::optional<B>>;},
  [](const auto& j)->MaybeV{return j.get<std::optional<C>>;}
};
for (const auto& p : ps)
  if (auto opt_result = p(i_j))
    return std::move(*opt_result);
throw ParseError("Can't parse");
}

尽管它可能确实可以简化,因为 lambda 只是类型不同,而我真正需要的是迭代 std::variant 类型。


1
除非我误解了您的需求,否则 std::visit 应该可以胜任。如果变量未初始化,则不应对其进行解析。如果您只是指没有值,那么这是一个特殊情况(请参见 std::variant::valueless_by_exception)。 - Cruz Jean
3
@CruzJean,我认为OP想要构建变量。因为它还没有被创建,所以没有什么可以访问的,它是从函数返回的东西。 - chris
@chris 解析函数在调用 j 上的各种 T 类型的 get<std::optional<T>>,其中 ji_j,即输入的 json 对象。这不应该已经初始化了吗?我们返回的只是第一个成功的解析器调用的结果(* 只是取消引用 std::optional)。 - Cruz Jean
1
@CruzJean,这个问题是在询问如何提供所有这些解析函数而不需要明确列出每种类型(A、B、C)。除了这些函数如何进行解析并具有清晰的模式以便从变量类型自动生成它们之外,它们的解析方式并不太相关。检查第一个有效结果的代码是正确的。 - chris
@chris 但是如果它与std::visit兼容,那么可以使用通用访问器直接返回A、B、C实例(或在没有匹配时抛出异常等)。这样就可以避免在任何地方指定任何类型。 - Cruz Jean
@CruzJean,我看到访问json对象有两个问题。首先,它需要支持访问,据我所知,这个库不支持。其次,似乎变量的替代方案可以使用.get<optional<T>>,但不能直接返回访问结果。也就是说,你可以有一个从“int”构造的类型“A”,但如果你访问json并得到一个“int”,你仍然必须通过“A”、“B”和“C”来检查是否可以从该“int”构造它们。如果你不打算访问json对象,那么我不确定你建议访问什么。 - chris
2个回答

9
你想要从0到变量大小减1的编译时整数,并可能提前退出迭代。
有很多方法可以获得编译时整数。我最喜欢的两种方法是生成一个整数常量元组,或者使用参数包调用续集。
采用整数常量元组版本,您可以使用“元组循环”依次访问每个元素。
template<std::size_t I>
using index_t = std::integral_constant<std::size_t, I>;
template<std::size_t I>
constexpr index_t<I> index{};

template<std::size_t...Is>
constexpr std::tuple< index_t<Is>... > make_indexes(std::index_sequence<Is...>){
  return std::make_tuple(index<Is>...);
}
template<std::size_t N>
constexpr auto indexing_tuple = make_indexes(std::make_index_sequence<N>{});

从变量大小你称之为。从那里,你调用了一个tuple_foreach。
如果解析成功并且尚未解析,则tuple_foreach会插入可选的返回值。
V parse(const json& j)
{
  auto indexes = indexing_tuple<tuple_size_v<V>>;
  std::optional<V> retval;
  tuple_foreach(indexes, [&](auto I){ // I is compile time integer
    if(retval) return;
    auto p = j.get<tuple_alternative_t<I>>();
    if(p) retval.emplace(std::move(*p));
  });
  if(!retval) throw ParseError("Can't parse");
  return std::move(*retval);
}

tuple_foreach 可以在互联网上找到,但为了完整起见:

template<std::size_t...Is, class T, class F>
auto tuple_foreach( std::index_sequence<Is...>, T&& tup, F&& f ) {
  ( f( std::get<Is>( std::forward<T>(tup) ) ), ... );
}
template<class T, class F>
auto tuple_foreach( T&& tup, F&& f ) {
  auto indexes = std::make_index_sequence< std::tuple_size_v< std::decay_t<T> > >{};
  return tuple_foreach( indexes, std::forward<T>(tup), std::forward<F>(f) );
}

这应该可以在中完成。


1
非常感谢!它起作用了(不幸的是,我谷歌了tuple_foreach的解决方案)。您发布的代码超出了我的能力水平,但我希望我能理解并在http://codereview.stackexchange.com/上发布其变体。如果您能够审查它,那将是太好了。 - Nestor

8

可以使用 if-constexpr 限制模板实例化,从0std::variant_size_v(不包括)递归处理类型:

#include <variant>
#include <optional>
#include <cstddef>
#include <utility>

using V = std::variant<A, B, C>;

template <std::size_t I = 0>
V parse(const json& j)
{
    if constexpr (I < std::variant_size_v<V>)
    {
        auto result = j.get<std::optional<std::variant_alternative_t<I, V>>>();

        return result ? std::move(*result) : parse<I + 1>(j);
    }
    throw ParseError("Can't parse");
}

DEMO


谢谢你的解决方案!我尝试想出类似的东西,但失败了。 - Nestor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接