Unique types variant

We have the std::variant class template which is a type-safe union.
We use it when we know the types in advance.
It can deduce what kind of variant we want from a constructor argument:

//create double variant
std::variant<int, double> v(1.3);
//create int variant
std::variant<int, float> v(35);

But we cannot do this:

//compiler will complain
std::variant<int, int> x(1);

So what when we know the list of types but it is not guaranteed they are unique and we need a variant of these types?

template<typename... T>
struct unique_types_variant
{
    // ... how to implement it?
};

There is a way to create a list of unique types given a list of types as variadic template parameters.

template<typename T>
struct contains_type
{};

template<typename... T>
struct unique_type_list : contains_type<T>...
{
    template<typename New>
    struct insert
    {
        static unique_type_list<T...> foo(contains_type<New>*);
        static unique_type_list<T..., New> foo(...);

        using type = decltype(foo(std::declval<unique_type_list<T...>*>()));
    };

    template<typename New>
    typename insert<New>::type operator + (New);
};

Now what happens when we use it like this:

using list1 = decltype(std::declval<unique_type_list<>>() + std::declval<int>());

It takes an empty list and adds int type to it.

But if we try to add another int to it, the insert implementation kicks in and chooses foo overload for contains_type<int>* argument, resulting in the exact same type.

using list1 = decltype(std::declval<unique_type_list<>>() + std::declval<int>());

using list2 = decltype(std::declval<list1<>>() + std::declval<int>());

static_assert(std::is_same_v<list1, list2>);

Now why did we bother to overload the + operator for the unique_type_list?
Because of fold expressions.

Now we use it like so:

template<typename... T>
struct make_unique_type_list
{
    using type = decltype((std::declval<unique_type_list<>>() + ... + std::declval<T>()));
};

Now we are just a single step from turning it into a variant.

template<typename List>
struct unique_types_list_to_variant
{};

template<typename... T>
struct unique_types_list_to_variant<unique_type_list<T...>>
{
    using type = std::variant<T...>;
};

template<typename... T>
using unique_types_variant_t = 
    typename unique_types_list_to_variant<
        typename make_unique_type_list<T...>::type
    >::type;

Now let’s test it.

using v1 = unique_types_variant_t<int, float>;
using v2 = unique_types_variant_t<int, float, int>;

static_assert(std::is_same_v<v1, v2>);
static_assert(std::is_same_v<v1, std::variant<int, float>>);

Leave a Comment