Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating a new boost-variant type from given nested boost-variant type

Assume that I have a nested boost::variant-type TNested containing some types and some other boost::variant types (that itself cannot contain again boost::variant types, so that there will be no recursion).

I'm looking for a template alias flatten<TNested> that would evaluate to a boost::variant type have no nested boost::variants, e.g. TFlatten, whereas possible duplicate types are being removed, e.g. int occurs only once.

I have really no idea, if this can be accomplished somehow.

#include <boost/variant.hpp>
#include <boost/any.hpp>
#include <iostream>

struct Person;

typedef boost::variant<int, double, boost::variant<std::string, int>, boost::variant<Person>> TNested;
typedef boost::variant<int, double, std::string, Person> TFlatten;

template<typename NestedVariant>
using flatten = //???

int main() {
    flatten<TNested> x; 
    std::cout << typeid(x) == typeid(TFlatten) << std::endl;
}
like image 960
Aleph0 Avatar asked Sep 01 '16 13:09

Aleph0


People also ask

What is boost :: Apply_visitor?

boost::apply_visitor — Allows compile-time checked type-safe application of the given visitor to the content of the given variant, ensuring that all types are handled by the visitor.

What is boost :: variant?

Boost. Variant, part of collection of the Boost C++ Libraries. It is a safe, generic, stack-based discriminated union container, offering a simple solution for manipulating an object from a heterogeneous set of types in a uniform manner.

What is boost any?

The boost::any class (based on the class of the same name described in "Valued Conversions" by Kevlin Henney, C++ Report 12(7), July/August 2000) is a variant value type based on the second category. It supports copying of any value type and safe checked extraction of that value strictly against its type.


2 Answers

wandbox example

Here's a valid C++11 recursive template-based solution that works with your example:

using nested = variant<int, double, variant<std::string, int>, variant<Person>>;
using flattened = variant<int, double, std::string, int, Person>;

static_assert(std::is_same<flatten_variant_t<nested>, flattened>{}, "");

General idea:

std::tuple is used as a generic type list and std::tuple_cat is used to concatenate type lists.

A flatten_variant<TResult, Ts...> recursive metafunction is defined which does the following:

  • TResult will get filled with flattened types and will be returned at the end of the recursion.

    • The recursion ends when Ts... is empty.
  • Non-variant types are appended to TResult.

  • Variant-types are unpacked, and all their inner types are recursively flattened, then appended to TResult.


Implementation:

namespace impl
{
    // Type of the concatenation of all 'Ts...' tuples.
    template <typename... Ts>
    using cat = decltype(std::tuple_cat(std::declval<Ts>()...));

    template <typename TResult, typename... Ts>
    struct flatten_variant;

    // Base case: no more types to process.
    template <typename TResult>
    struct flatten_variant<TResult>
    {
        using type = TResult;
    };

    // Case: T is not a variant.
    // Return concatenation of previously processed types,
    // T, and the flattened remaining types.
    template <typename TResult, typename T, typename... TOther>
    struct flatten_variant<TResult, T, TOther...>
    {
        using type = cat<TResult, std::tuple<T>,
                         typename flatten_variant<TResult, TOther...>::type>;
    };

    // Case: T is a variant.
    // Return concatenation of previously processed types,
    // the types inside the variant, and the flattened remaining types.    
    // The types inside the variant are recursively flattened in a new
    // flatten_variant instantiation.
    template <typename TResult, typename... Ts, typename... TOther>
    struct flatten_variant<TResult, variant<Ts...>, TOther...>
    {
        using type =
            cat<TResult, typename flatten_variant<std::tuple<>, Ts...>::type,
                typename flatten_variant<TResult, TOther...>::type>;
    };

    template<typename T>
    struct to_variant;

    template<typename... Ts>
    struct to_variant<std::tuple<Ts...>>
    {
        using type = variant<Ts...>;
    };
}

template <typename T>
using flatten_variant_t =
    typename impl::to_variant<
        typename impl::flatten_variant<std::tuple<>, T>::type
    >::type;
like image 190
Vittorio Romeo Avatar answered Oct 19 '22 07:10

Vittorio Romeo


The problem with boost::variant is that it isn't the real variadic template only simulating one by adding a number of template parameters with default value boost::detail::variant::void_. To work with it as with variadic template, one need to change it first to the pack with relevant types. The full c++11 ready to use approach to do what you want could look as follows. The approach takes into account both flatness as well as uniqueness assumption to the result type. The integer_sequence part will be unnecessary if you decide to use c++14:

#include <boost/variant.hpp>
#include <tuple>
#include <type_traits>

template <class T, T... Vs>
struct integer_sequence { };

template <class T, class, class, class = integer_sequence<T>, class = integer_sequence<T, 0>, class = void>
struct make_integer_sequence_impl;

template <class T, T ICV1, T... Res, T... Pow>
struct make_integer_sequence_impl<T, std::integral_constant<T, ICV1>, std::integral_constant<T, 0>, integer_sequence<T, Res...>, integer_sequence<T, Pow...>, typename std::enable_if<(ICV1 > 0)>::type>: make_integer_sequence_impl<T, std::integral_constant<T, ICV1/2>, std::integral_constant<T, ICV1%2>, integer_sequence<T, Res...>, integer_sequence<T, Pow..., (Pow + sizeof...(Pow))...>> { };

template <class T, T ICV1, T... Res, T... Pow>
struct make_integer_sequence_impl<T, std::integral_constant<T, ICV1>, std::integral_constant<T, 1>, integer_sequence<T, Res...>, integer_sequence<T, Pow...>, void>: make_integer_sequence_impl<T, std::integral_constant<T, ICV1/2>, std::integral_constant<T, ICV1%2>, integer_sequence<T, Pow..., (Res + sizeof...(Pow))...>, integer_sequence<T, Pow..., (Pow + sizeof...(Pow))...>> { };

template <class T, class Res, class Pow>
struct make_integer_sequence_impl<T, std::integral_constant<T, 0>, std::integral_constant<T, 0>, Res, Pow, void> {
   using type = Res;
};

template <class T, T V>
using make_integer_sequence = typename make_integer_sequence_impl<T, std::integral_constant<T, V/2>, std::integral_constant<T, V%2>>::type;

template <class>
struct is_variant_void {
   static constexpr size_t value = 0;
};

template <>
struct is_variant_void<boost::detail::variant::void_> {
   static constexpr size_t value = 1;
};

constexpr size_t sum(size_t v){
    return v;
}

template <class... Args>
constexpr size_t sum(size_t v, Args... vs){
    return v + sum(vs...);
}

template <class Variant>
struct variant_size;

template <class... Ts>
struct variant_size<boost::variant<Ts...>> {
   static constexpr size_t value = sizeof...(Ts) - sum(is_variant_void<Ts>::value...);
};

template <class...>
struct pack { };

template <class V, class=make_integer_sequence<size_t, variant_size<V>::value>>
struct variant_to_pack;

template <class... Ts, size_t... Is>
struct variant_to_pack<boost::variant<Ts...>, integer_sequence<size_t, Is...>> {
    using type = pack<typename std::tuple_element<Is, std::tuple<Ts...>>::type...>;
};

template <class>
struct unique_opaque { };

template <class... Ts>
struct unique_pack: unique_opaque<Ts>... { };

template <class, class, class = void>
struct unique;

template <class... Res, class T, class... Ts>
struct unique<unique_pack<Res...>, pack<T, Ts...>, typename std::enable_if<std::is_base_of<unique_opaque<T>, unique_pack<Res...>>::value>::type>:
       unique<unique_pack<Res...>, pack<Ts...>> { };

template <class... Res, class T, class... Ts>
struct unique<unique_pack<Res...>, pack<T, Ts...>, typename std::enable_if<!std::is_base_of<unique_opaque<T>, unique_pack<Res...>>::value>::type>:
       unique<unique_pack<Res..., T>, pack<Ts...>> { };

template <class... Res>
struct unique<unique_pack<Res...>, pack<>, void> {
    using type = boost::variant<Res...>;
};

template <class...>
struct flatten;

template <class... Res, class T, class... Rest>
struct flatten<pack<Res...>, T, Rest...>: flatten<pack<Res..., T>, Rest...> { };

template <class... Res, class... Vs, class... Rest>
struct flatten<pack<Res...>, boost::variant<Vs...>, Rest...>: flatten<pack<Res...>, typename variant_to_pack<boost::variant<Vs...>>::type, Rest...> {};

template <class... Res, class... Vs, class... Rest>
struct flatten<pack<Res...>, pack<Vs...>, Rest...>: flatten<pack<Res...>, Vs..., Rest...> { };

template <class Res>
struct flatten<Res>:unique<unique_pack<>, Res> { };

int main() {
   boost::variant<int, boost::variant<float, int, boost::variant<char, bool>, bool>> vif;
   static_assert(std::is_same<flatten<pack<>, decltype(vif)>::type, boost::variant<int, float, char, bool>>::value, "Test");
}
like image 32
W.F. Avatar answered Oct 19 '22 07:10

W.F.