Search code examples
c++templatesvariadic-templates

How do I conditionally expand the types of a container from within a variadic template?


I'm trying to create a wrapper for variant that allows me to wrap a bunch of types passed in another type, and put them into the variant. How would I expand a tuple's types and place them into the variant conditionally based on whether the type is a container?

template <typename T>
struct Wrapper
{};

template <typename>
struct has_expandable_types : std::false_type
{
};

template <template <typename...> class T, typename... Args>
struct has_expandable_types<T<Args...>> : std::true_type
{
};

template <template <typename> class T, typename... Types>
struct Variant_wrapper;
template <template <typename> class T, typename U, typename... Types>
struct Variant_wrapper_expanded;

template <template <typename> class T, typename... Types>
struct Variant_wrapper : std::variant<T<Types>...>
{
};

template <template <typename> class T, template <typename...> class U, typename... UTypes>
struct Variant_wrapper_expanded <T, U<UTypes...>> : Variant_wrapper<T, UTypes...>
{
};

using Types = std::tuple<int, float>;

int main()
{
    //std::variant< Wrapper<int>, Wrapper<float> >, OK
    Variant_wrapper<Wrapper, int, float> var; 

    //std::variant< Wrapper<int>, Wrapper<float> >, OK
    Variant_wrapper_expanded<Wrapper, Types> var2;

    //std::variant< Wrapper<int>, Wrapper<float> >, NOT OK
    // EXPECTED std::variant< Wrapper<int>, Wrapper<float>, Wrapper<double> >
    //can't capture a second parameter pack?
    Variant_wrapper_expanded<Wrapper, Types, double> var3;
}

The ideal usage would be something like

//produces std::variant< Wrapper<std::string>, Wrapper<int>, Wrapper<float>, Wrapper<double> >
Variant_wrapper<Wrapper, std::string, Types, double> var; 
  1. In the var3 example, it doesn't look like I can have a second parameter pack to capture the remaining types after the tuple even though UTypes is only for the container types. How can I capture the remaining types?
  2. Passing a tuple type to Variant_wrapper wraps the entire tuple but I'd like to be able to expand the held types and insert them into the variant instead. How would I do the expansion?

Solution

  • With C++17 CTAD, you might do:

    // Tuple_expander<A, std::tuple<B, C>, D>::type -> std::tuple<A, B, C, D> 
    template <typename... Ts>
    struct Tuple_expander
    {
        using type = decltype(std::tuple_cat(std::tuple{std::declval<Ts>()}...));
    // trick is here:
    // std::tuple{std::tuple{..}} use copy constructor
    // std::tuple{..} wrap the type in a tuple.
    };
    
    // The mapping/transformation of the (flat) tuple
    template <template <typename> class C, typename T> struct TupleToVariantWrapper;
    
    template <template <typename> class C, typename... Ts>
    struct TupleToVariantWrapper<C, std::tuple<Ts...>>
    {
        using type = std::variant<C<Ts>...>;
    };
    
    // No real changes.
    template <template <typename> class C, typename... Ts>
    struct Variant_wrapper
    {
        using type = std::variant<C<Ts>...>;
    };
    
    template <template <typename> class C, typename... Ts>
    struct Variant_wrapper_expanded
    {
        using type = typename TupleToVariantWrapper<C, typename Tuple_expander<Ts...>::type>::type;
    };
    

    (I use ::type instead of inheritance)

    Demo