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
.
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.
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.
You can loop through the tuple items by using a for loop.
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.
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With