【问题标题】:How to iterate over the types of std::variant?如何迭代 std::variant 的类型?
【发布时间】:2019-08-24 23:06:11
【问题描述】:

我有一些变体using V = std::variant<A, B, C> 和一个带有原型V parse(const json&) 的函数。该函数应该尝试解析所有类型(例如AB,然后是C)直到第一次成功(并且它应该隐式地解析,因为及时会有很多类型)。

如何实现这种东西?

我们可能会以某种方式使用std::variant_size

Here 是我所需要的。

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

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");
}

但它肯定会被简化,因为 lambdas 仅在类型上有所不同,而我真正需要的是迭代 std::variant 的类型。

【问题讨论】:

  • 除非我误解了你的需要,std::visit 应该可以工作。如果变量未初始化,则不应解析它。如果您只是说没有价值,那是一种特殊情况(请参阅std::variant::valueless_by_exception
  • @CruzJean,在我看来,OP 想要构建变体。没有什么可访问的,因为它还没有被创建,它是从函数返回的东西。
  • @chris 解析函数正在为j 上的各种T 类型调用get&lt;std::optional&lt;T&gt;&gt;,即i_j,输入json 对象。那不应该已经初始化了吗?我们返回的只是第一次成功调用解析器的结果(* 只是取消引用 std::optional
  • @CruzJean,问题是如何提供所有这些解析功能,而无需再次明确列出每种类型(A、B、C)。函数如何进行解析并不太相关,除了它们有一个清晰的模式,这有助于从变体的类型自动生成它们。检查第一个有效结果的代码很好。
  • @chris 但如果它与std::visit 兼容,则可以使用通用访问者并直接返回 A、B、C 实例(或不返回等)。这将避免指定 anywhere 类型的 any

标签: c++ c++17 template-meta-programming variant nlohmann-json


【解决方案1】:

您希望编译时间整数从 0 到变体的大小减 1,并可能提前退出对它们的迭代。

有很多方法可以获取编译时间整数。我最喜欢的两个是生成一个整数常量元组,或者使用一个整数常量参数包调用一个延续。

采用整数常量的元组版本,可以使用“tuple for each”依次访问每个。

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

应该在中做到这一点。

【讨论】:

  • 非常感谢!它起作用了(不幸的是,我用谷歌搜索了 tuple_foreach 的解决方案)。您发布的代码超出了我的能力水平,但我希望我能理解它并在codereview.stackexchange.com 上发布它的变体。如果你能复习一下就好了。
【解决方案2】:

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

#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

【讨论】:

  • 感谢您的解决方案!我试图想出这样的东西,但失败了(
猜你喜欢
  • 2017-02-01
  • 2011-01-11
  • 1970-01-01
  • 2018-08-05
  • 1970-01-01
  • 2021-09-20
  • 1970-01-01
  • 1970-01-01
  • 2019-05-13
相关资源
最近更新 更多