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

Nes*_*tor 6 c++ variant template-meta-programming c++17 nlohmann-json

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");
}
Run Code Online (Sandbox Code Playgroud)

但是,它肯定可以简化,因为lambda的类型仅不同,而我真正需要的是迭代的类型std::variant

Pio*_*cki 8

0类型可以从到(不包括)递归处理std::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");
}
Run Code Online (Sandbox Code Playgroud)

演示版


Yak*_*ont 7

您希望编译时整数从 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>{});
Run Code Online (Sandbox Code Playgroud)

从你称之为的变体大小。从那你调用一个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);
}
Run Code Online (Sandbox Code Playgroud)

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) );
}
Run Code Online (Sandbox Code Playgroud)

应该在做到这一点。