C++ separate structs in to "groups" but allow `std::variant` to see all structs across the "groups"

Viewed 81

Please see code below.

I have 3 structs (A, B and C) defined across 2 groups (in reality I have many more). The groups are to provide context within the real-world domain i'm modelling. These groups are implemented via two std::variants (Group1 and Group2).

I have a function which can return any of the underlying structs.

To provide a single return type for the function I create a third std::variant (AllGroups) from my initial two variants (Group1 and Group2).

However, my problem is AllGroups cannot see A, B or C, only Group1 and Group2.

Is there a way to keep the structs defined with some sort of group but also collapse them in to one std::variant for the function return type?

#include <variant>
#include <iostream>

struct A{}; struct B{};
using Group1 = std::variant<A, B>;

struct C{};
using Group2 = std::variant<C>;

using AllGroups = std::variant<Group1, Group2>;

AllGroups getObject()
{
    A a;     // Simple logic for the general question
    AllGroups ret(a);
    return ret;
}

int main()
{
    AllGroups ret = getObject();

    // Here i'd like to check for the individual structs, not Group1 and Group2
    // However I get a compiler error because the AllVariant can only see Group1 and Group2

    std::cout << std::holds_alternative<A>(ret) << std::endl;
}
3 Answers

Here is how you can do a trait which produces a type you want - a combination of variant's alternative types. Unlike the other answers, it works with more than two variants being joined ;)

#include <variant>


template<class Variant1, class Variant2, class... Variants> 
struct variant_cat;

template<class... Var1, class... Var2>
struct variant_cat<std::variant<Var1...>, std::variant<Var2...>>
{
    using type = std::variant<Var1..., Var2...>;
};

template<class... Var1, class... Var2, class... Variants>
struct variant_cat<std::variant<Var1...>, std::variant<Var2...>, Variants...>
{
    using type = typename variant_cat<std::variant<Var1..., Var2...>, Variants...>::type;
};

template<class... A>
using variant_cat_t = variant_cat<A...>::type;

// Usage:
variant_cat_t<std::variant<int, char>, std::variant<double, float>, std::variant<std::nullptr_t>> Result;

You can use a trait to combine std::variant<A...> with std::variant<B...> to get std::variant<A...,B...>:

#include <variant>
#include <iostream>

struct A{}; struct B{};
using Group1 = std::variant<A, B>;

struct C{};
using Group2 = std::variant<C>;

template <typename T1,typename T2> struct combine_variants;

template <typename... A,typename... B>
struct combine_variants<std::variant<A...>,std::variant<B...>> {
    using type = std::variant<A...,B...>;
};


using AllGroups = combine_variants<Group1, Group2>::type;

AllGroups getObject()
{
    A a;     // Simple logic for the general question
    AllGroups ret(a);
    return ret;
}

int main()
{
    AllGroups ret = getObject();

    std::cout << std::holds_alternative<A>(ret) << std::endl;
}

Live Demo

Here is another approach to concatenate two variant types together to get a merged type

template <typename... Ts, typename... Us>
auto variant_concat(std::variant<Ts...>, std::variant<Us...>) -> std::variant<Ts..., Us...>;

template <typename V1, typename V2>
using variant_concat_t = decltype(variant_concat(std::declval<V1>(), std::declval<V2>()));

and you would use it like

using AllGroups = variant_concat_t<Group1, Group2>;
Related