C ++: combining two types without inheriting a virtual base class

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

The problem is that the intersection class in my context is completely pointless, so creating it confuses code users.

My practical case: I describe a digital hardware simulator, which is a hierarchical tree 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 these two types:

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

I suppose there must be some technical method to create a union type (this is the question I ask):

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

But I did not find. The only solution I found on the Internet 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; } 
+5
source share
2 answers

You can use the usage declaration and push the field c from one of two structures.
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{}; } 

This is not exactly a combination of the two types, but helps eliminate the use of c through top_level_module .
From a users perspective, top_level_module looks like this: it has 5 explicit and accessible fields named a , b , c , d and e .
In addition, it has an additional data item named c and is accessible through its full name:

 mod.module1::c 

In other words, the item was not deleted; it was hidden using the using declaration in the top-level class.

This approach has several disadvantages. As an example:

  • You are not actually eliminating the extra field. Therefore, it will be initialized by default and you should be aware of the consequences of this.
  • top_level_module users can somehow use a hidden element.
  • You cannot initialize aggregate for top_level_module .
  • ...

If you want to go a little further and restrict access to the base classes, you can use private inheritance and explicitly export the necessary fields:

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

Really.

+4
source

Since you cannot verify the name of participants without a template, you will need to make the intersection in a different way.

In fact, you cannot (yet) verify from which structure. But with tuples you can. And you can make a union if each member of the tuple has different types.

Start with a simple shell for your port type:

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

Then determine which modules are created from 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 calculate 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 as follows:

 using top_level_module = module_union<module1, module2>; 

Live at coliru

Additionnaly, you can make a utility that builds a traditional structure from a tuple of ports.

It was fun.

+4
source

Source: https://habr.com/ru/post/1261021/


All Articles