📜 ⬆️ ⬇️

Processing the structure of the list of basic types

I want to tell how we used lists of basic types for processing messages. Messages are structures inherited from small base structures. All useful information is stored in the basic structures. For processing, you need to know from which basic structures the message being processed was inherited. Everything you need to work with type lists we found in Boost.MPL. I chose boost :: mpl :: vector as the list of types. To go through the list of types boost :: mpl :: for_each .

The source data here is the same as in the previous article .
Hidden text
struct Base1 {}; struct Base2 {}; struct Base3 {}; struct Derived12: public Base1, public Base2 {}; struct Derived23: public Base2, public Base3 {}; 
In reality, we have much more basic structures and messages created on their basis.

In the simplest version, for boost :: mpl :: for_each, you need to specify a list of types as a template parameter, and a class with a method operator () (T), where T is a type from the list as an argument. You can make the method template, but this is not exactly what we need. Therefore, we will overload operator () for all base structures. The list of types is now manually announced in each message. In the first approximation, we get:
 struct Derived12: public Base1, public Base2 { boost::mpl::vector<Base1, Base2> types; }; struct Derived23: public Base2, public Base3 { boost::mpl::vector<Base2, Base3> types; }; class Describer { public: void operator()(Base1) { std::cout << "   Base1\n"; } void operator()(Base2) { std::cout << "   Base2\n"; } void operator()(Base3) { std::cout << "   Base3\n"; } }; void main() { Derived12 d12; boost::for_each<Derived12::types>(Describer()); } 

As a result of the performance will
displayed
 Getting information from Base1
 Getting information from Base2

This problem has two problems:
  1. The value of d12 is not used at all;
  2. The function boost :: mpl :: for_each creates instances of the base structures.

With the first problem, everything is simple - let the value be transmitted and stored on the Describer constructor, and the class itself will be template. The second problem is more serious, since in addition to the cost of creating objects, there are additional restrictions on structures - they must have a constructor without parameters and cannot be abstract. I decided to overload operator () with a pointer. In this case, the type list must contain pointers to types or you can use the second option for_each, with the transfer of the template for transformation, and use this option. As a template for the transformation, take boost :: add_pointer. To check that all base structures are processed, add a template operator () containing BOOST_STATIC_ASSERT (false). This will give a compilation error if a new base structure appears. As a result, we get:
 template<typename T> class Describer { public: Describer(const T& v): v(v) {} void operator()(Base1*) { std::cout << "   Base1\n"; } void operator()(Base2*) { std::cout << "   Base2\n"; } void operator()(Base3*) { std::cout << "   Base3\n"; } template<typename U> void operator()(U*) { BOOST_STATIC_ASSERT(false); } private: const T& v; }; void main() { Derived12 d12; boost::for_each< Derived12::types, boost::add_pointer<boost::mpl::_> > ( Describer<Derived12>(d12) ); } 


Now we will try to simplify the establishment of lists of types involved in inheritance. Let's declare the full list of types of base structures and use the algorithm boost :: mpl :: copy_if . Which will copy in the new list all elements meeting the specified condition. As a condition, we take the inheritance check boost :: is_base_of .
 typedef boost::mpl::vector<Base1, Base2, Base3> FullTypesList; template<typename T, typename BaseList> struct MakeTypesList { typedef typename boost::mpl::copy_if< BaseList, boost::is_base_of< boost::mpl::_, T > >::type TypesList; }; 


For convenience, we add in Describer operator () without parameters, which will call for_each.
 void Describer::operator()() { boost::mpl::for_each< typename MakeTypesList<T,FullTypesList>::TypesList, add_pointer<boost::mpl::_> >( boost::ref(*this) ); } 

The wrapper boost :: ref is needed so that the describer copy operator is not called.
')
Final version
 struct Base1 {}; struct Base2 {}; struct Base3 {}; typedef boost::mpl::vector<Base1, Base2, Base3> FullTypesList; template<typename T, typename BaseList> struct MakeTypesList { typedef typename boost::mpl::copy_if< BaseList, boost::is_base_of< boost::mpl::_, T > >::type TypesList; }; template<typename T> class Describer { public: Describer(const T& v): v(v) {} void operator()() { boost::mpl::for_each< typename MakeTypesList<T,FullTypesList>::TypesList, add_pointer<boost::mpl::_> >( boost::ref(*this) ); } void operator()(Base1*) { std::cout << "   Base1\n"; } void operator()(Base2*) { std::cout << "   Base2\n"; } void operator()(Base3*) { std::cout << "   Base3\n"; } template<typename U> void operator()(U*) { BOOST_STATIC_ASSERT(false); } private: const T& v; }; //   Derived12  Derived23   . struct Derived12: public Base1, public Base2 {}; struct Derived23: public Base2, public Base3 {}; void main() { Derived12 mes12; ( Describer<Derived12>(mes12) )(); } 



If there are a lot of classes processing structures in this way, it is more reasonable to declare lists of base classes for messages separately. It turned out that the message structure is not used independently - it is the base class for the template class that implements the common interface of all messages and in it we define the list of basic types. We call this list when calling for_each. You can make a wrapper pattern and use it. Simple option
wrapper pattern
 template<typename T> struct Wrapper: public T { typedef typename MakeTypesList<T, FullTypesList>::TypesList TypesList; } void Describer::operator() { boost::mpl::for_each< typename T::TypesList, add_pointer<boost::mpl::_> >( boost::ref(*this) ); } 


Update:
Note for BOOST_STATIC_ASSERT in the template Describer :: operator ()
In this example, G ++ will generate a compilation error on BOOST_STATIC_ASSERT (false). G ++, unlike MS Visual C ++, checks the template body, even if it is not instantiated. All names that are independent of the template parameter must be known at the time the template is defined. If any construction causes a compilation error and does not depend on the template parameter, then the compilation error will be. You can do the following:
 template <typename T> struct Describer { template<typename U> void operator() { BOOST_STATIC_ASSERT(sizeof(U) == 0); } } 



Update2: Thanks to nickolaym for interesting comments with the option of automatically generating a list of base classes.

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


All Articles