Search code examples
c++variadic-templatestemplate-meta-programmingboost-mplboost-variant

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

Solution

  • 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;