Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ : union of two types without virtual base class inheritance

Tags:

c++

c++14

Is it possible to create a union of two types without manually creating an intersection type?

The problem is that intersection class in my context is completely meaningless, so creating it will confuse code users.

My practical case: I'm describing a digital hardware simulator that is a hierarchical tree-like structure of many modules:

class port;

class module0 {
    port a,b,c;
}

class module1 {
    port c,d,e;
}

I need to create a union of this two types:

class top_level_module {
    port a,b,c,d,e;
}

I imagine that there should be some techique to create a union type (this is the question I'm asking):

class top_level_module : union_type < module0, module1 > {
    //  port a,b,c,d,e;
}

But I had not found any. The only solution I've found on the web is virtual inheritance:

// this is a meaningless type in my context
class intersection_of_module0_module1 { 
    port c;
}

class module0: virtual intersection_of_module0_module1 {
    port a,b;
}

class module1: virtual intersection_of_module0_module1 {
    port d,e;
}

class top_level_module : module0, module1 {
    //  port a,b,c,d,e;
}
like image 1000
random Avatar asked Dec 08 '16 21:12

random


2 Answers

Since you can't inspect the name of members without boilerplate, you will need to make the intersection in another way.

Indeed, you cannot (yet) inspect what a struct is made of. But with tuples you can. And you can make up the union if every members of the tuple has different types.

Let's start with a simple wrapper for your port type:

template<char name>
struct port_wrapper : port {};

Then, define what your modules are made of using tuples:

template<char... names>
using module = std::tuple<port_wrapper<names>...>;

using module1 = module<'a', 'b', 'c'>;
using module2 = module<'c', 'd', 'e'>;

After that, you can use metafunctions to compute the intersection of the module:

template<char...>
struct name_list {};

template<typename>
struct to_name_list_impl;

template<char... names>
struct to_name_list_impl<std::tuple<port_wrapper<names>...>> {
    using list = name_list<names...>;
};

template<typename module>
using to_name_list = typename to_name_list_impl<module>::list;

template <char, typename>
struct name_list_contains;

template <char c>
struct name_list_contains<c, name_list<>> : std::false_type {};

template <char c, char head, char... tail>
struct name_list_contains<c, name_list<head, tail...>> : name_list_contains<c, name_list<tail...>> {};

template <char c, char... tail>
struct name_list_contains<c, name_list<c, tail...>> : std::true_type {};

template<typename, typename>
struct name_list_concat;

template<char... names1, char... names2>
struct name_list_concat<name_list<names1...>, name_list<names2...>> {
    using list = name_list<names1..., names2...>;
};

template<typename...>
struct all_names;

template<>
struct all_names<> {
    using list = name_list<>;
};

template<char... names, typename... Tail>
struct all_names<name_list<names...>, Tail...> {
    using list = typename name_list_concat<name_list<names...>, typename all_names<Tail...>::list>::list;
};

template<typename>
struct unique_names;

template<>
struct unique_names<name_list<>> {
    using list = name_list<>;
};

template<char first, char... others>
struct unique_names<name_list<first, others...>> {
    using uniques = typename unique_names<name_list<others...>>::list;

    using list = std::conditional_t<
        name_list_contains<first, uniques>::value,
        uniques,
        typename name_list_concat<uniques, name_list<first>>::list
    >;
};

template<typename>
struct module_union_impl;

template<char... names>
struct module_union_impl<name_list<names...>> {
    using module_name_union = module<names...>;
};

template<typename... modules>
using module_union = typename module_union_impl<
    typename unique_names<
        typename all_names<
            to_name_list<modules>...
        >::list
    >::list
>::module_name_union;

With this code, you can now use module_union like this:

using top_level_module = module_union<module1, module2>;

Live at Coliru

Additionnaly, one could make an utility that construct a traditional struct from a tuple of ports.

That was fun.

like image 26
Guillaume Racicot Avatar answered Nov 10 '22 18:11

Guillaume Racicot


You can use an using declaration and promote the c field from one of the two structs.
As an example:

struct port {};

struct module0 { port a, b, c; };
struct module1 { port c, d, e; };


struct top_level_module: module0, module1 {
    using module0::c;
};

int main() {
    top_level_module mod;
    mod.c = port{};
}

It is not exactly an union of the two types, but it helps to disambiguate the uses of c through a top_level_module.
From the point of view of the users, top_level_module looks like it has 5 explicit and accessible fields named a, b, c, d and e.
Moreover, it has an extra data member named c and accessible through a fully qualified name:

mod.module1::c

In other terms, the member has not been deleted, it has been hidden by the using declaration in the top level class.

This approach has a few drawbacks. As an example:

  • You are not actually eliminating the extra field. Therefore it will be default initialized and you must be aware of the consequences of this.
  • Users of top_level_module can still use the hidden member somehow.
  • You cannot go with aggregate initialization for top_level_module.
  • ...

If you want to go a bit further and limit the access to the underlying classes, you can use private inheritance and export explicitly the required fields:

struct top_level_module: private module0, private module1 {
    using module0::a;
    using module0::b;
    using module0::c;
    using module1::d;
    using module1::e;
};

Verbose indeed.

like image 73
skypjack Avatar answered Nov 10 '22 17:11

skypjack