📜 ⬆️ ⬇️

How I Boost.Any with Boost.mpl was friends

Recently, I had to work with code in which the task of passing parameters of arbitrary types was solved using standard STL containers parameterized with the type boost :: any.
For example:
void foo (std::vector<boost::any>& args) { // do smth. } 

The previous developer was not very careful and inside the function, working with the contents of boost :: any was based on an assumption about the original data type, that is, if the conversion boost :: any_cast did not pass, then the parameter was skipped. In certain cases, this method of processing is acceptable and examples of this method of work can be found here .
However, I wanted to somewhat generalize the initial data type assumptions.

Imagine the situation that the data of the following types lies in std :: vector:
short, int, float, double, and there is a function
  double sum (std::vector<boost::any>& args) { } 

which calculates the sum of the values ​​of the parameters passed, calling boost :: any_cast <double> for each element of the container.
Let's see what happens when converting boost :: any_cast
  template<typename ValueType> ValueType * any_cast(any * operand) { // // some code skipped // operand->type() == typeid(ValueType) ? &static_cast<any::holder<ValueType> *>(operand->content)->held : 0; } 

If the type_info values ​​of the source type and the type to which the conversion is performed do not match, then it returns 0. But, returning to the sum function, in the normal situation we are quite able to write
  double value=1+2L+3.0F+4.0; 

thus adding up int, long, float and double. We want the same behavior from the sum function.

That is, when processing the value of boost :: any, we want to say: “Hey, the function 'Smth cast (any)', if you can, convert any to type Smth”. And in order for the cast function to know what types it can lead to Smth, we will need type lists. In essence, we want to specify a list of types that can be safely reduced to the required type.
To describe the list of convertible types, we will not reinvent the wheel, but take the boost :: mpl library and find the concept of type sequences we need.

Now we need a type converter class as a replacement for boost :: any_cast, as well as a type converter class manager, which
  1. Would accept the type to which we want to perform a ToType conversion.
  2. I would accept a list of CompatibleTypes types from which conversion to the required ToType type is possible.
  3. Based on the object boost :: any would return us a suitable type converter.

The converter class must provide us with the function of converting to the required type, and also store a pointer to a type_info object of the type from which it can perform the conversion:
  template<class ToType> class Converter { public: virtual ~Converter {} virtual ToType cast(const boost::any& arg) = 0; explicit Converter(const type_info* info) : m_info(info) { } const type_info& type() { return *m_info; } private: const type_info* m_info; }; 

Why the cast function is declared purely virtual will become clear below. Now we describe the dispatcher class:
  template<class CompatibleTypes, class ToType> class TypesDispatcher { std::vector<Converter<ToType>* > converters; struct wrapper { //code skipped }; public: TypesDispatcher(); Converter<ToType>* getConverter(const boost::any& arg); }; 

The dispatcher class is parametrized by the sequence of valid types CompatibleTypes and the type to which the ToType conversion is performed. The class stores the pointer container on the Converter. We need each Converter to be bound to one of the types listed in CompatibleTypes. To do this, inherit from Converter and implement the cast function:
  template <class FromType, class ToType> class TypeConverter : public Converter<ToType> { public: TypeConverter() : Converter(&typeid(FromType)) { } virtual ToType cast(const boost::any& arg) { return static_cast<ToType>(*boost::any_cast<FromType>(&arg)); } }; 

Now we have everything to create object converters for all types in CompatibleTypes, and also to implement the getConverter function.
  //      struct wrapper { explicit wrapper(std::vector<Converter<ToType>* >& converters) : m_converters(converters) { } template<class FromType> void operator()(FromType) { m_converters.push_back(new TypeConverter<FromType, ToType>()); } std::vector<Converter<ToType>* >& m_converters; }; //    TypesDispatcher() { boost::mpl::for_each<CompatibleTypes>(wrapper(converters)); } 

To go through the CompatibleTypes type list, we used boost :: mpl :: for_each, which takes an object the function that is applied to each type in the list at runtime by instantiating an object of each type.
The implementation of the getConverter function is simple and obvious:
  Converter<ToType>* getConverter(const boost::any& arg) { std::vector<Converter<ToType>* >::const_iterator it = converters.begin(); while(it != converters.end() ) { Converter<ToType>* converter = *it; if(converter->type() == arg.type()) return converter; ++it; } return 0; } 

That's all. Now we can write something like
  template<class CompatibleTypes, class ReturnType> ReturnType sum(std::vector<boost::any>& args) { TypesDispatcher<CompatibleTypes, ReturnType> dispatcher; std::vector<boost::any>::size_type i; ReturnType result = ReturnType(); for(i=0; i < args.size(); i++) { Converter<ReturnType>* converter = dispatcher.getConverter(args[i]); if(converter) result += converter->cast(args[i]); } return result; } int main(int argc, char* argv[]) { typedef boost::mpl::vector<int,long, float, double> types; std::vector<boost::any> seq; seq.push_back(boost::any( 1 )); seq.push_back(boost::any( 2L )); seq.push_back(boost::any( 3.0F )); seq.push_back(boost::any( 4.0 )); double result = sum<types, double>(seq); } 

')
A complete code example can be viewed here .

As a result, we were able to work with arguments of arbitrary types, relatively flexibly setting the rules for their conversion.

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


All Articles