Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to iterate over the types of std::variant?

I have some variant using V = std::variant<A, B, C> and a function with the prototype V parse(const json&). The function should try to parse all the types (e.g. A, B, then C) till the first success (and it should do it implicitly, for there will be many types in time).

How to implement something of this kind?

We may use std::variant_size somehow.

Here is something close to what I need.

My solution is to list parsers of all the types explicitly.

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

Yet it may definitely be simplified, for the lambdas different only in type and what I actually need is to iterate over the types of std::variant.

like image 851
Nestor Avatar asked Aug 24 '19 23:08

Nestor


People also ask

Does std :: variant allocate?

One additional thing is that std::variant does not dynamically allocate memory for the values. Any instance of std::variant at any given time either holds a value of one of its alternative types, or it holds no value at all.

How do you iterate through a std queue?

If you need to iterate over a std::queue , you can create a copy of it and remove items from the copy, one at a time, using the standard pop function after processing it. This way the original queue remains untouched, but its copy becomes empty.

How do you iterate through a tuple element?

You can loop through the tuple items by using a for loop.

Can you iterate through a tuple C++?

A C++ tuple is a container that can store multiple values of multiple types in it. We can access the elements of the tuple using std::get(), but std::get() always takes a constant variable parameter, so we can not simply iterate through it using a loop.


2 Answers

You want compile time integers from 0 to variant's size minus 1, and possibly early exit from iterating over them.

There are lots of ways to get compile time integers. Two of my favorites are generating a tuple of integral constants, or calling a continuation with a parameter pack of integral constants.

Taking the tuple of integral constants version, you can use a "tuple for each" to visit each in turn.

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

from variant size you call that. From that you call a tuple_foreach.

The tuple_foreach emplaces the optional return value if parsing succeeds and it wasn't already parsed.

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 can be found on the internet, but for completeness:

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

which should do it in c++17.

like image 79
Yakk - Adam Nevraumont Avatar answered Sep 23 '22 15:09

Yakk - Adam Nevraumont


Types can be processed recursively from 0 to std::variant_size_v (exclusive), with an if-constexpr limiting template instantiations:

#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

like image 28
Piotr Skotnicki Avatar answered Sep 22 '22 15:09

Piotr Skotnicki