Недавно мне пришлось работать с кодом, в котором задача передачи параметров произвольных типов решена с использованием стандартных STL контейнеров, параметризованных типом boost::any.
Например:
void foo (std::vector<boost::any>& args) {
// do smth.
}
Предыдущий разработчик был не очень аккуратен и внутри функции работа с содержимым boost::any основывалась на предположении об исходном типе данных, то есть если преобразование boost::any_cast не проходило, то параметр пропускался. В определенных случаях такой способ обработки приемлем и примеры этой методики работы можно посмотреть тут.
Однако, мне хотелось несколько обобщить исходные предположения о типе данных.
Представим себе ситуацию, что в std::vector лежат данные следующих типов:
short, int, float, double, и есть функция
double sum (std::vector<boost::any>& args) {
}
которая рассчитывает сумму значений переданных параметров, вызывая boost::any_cast<double> для каждого элемента контейнера.
Посмотрим, что происходит при преобразовании 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;
}
Если значения type_info исходного типа и типа, к которому производится преобразование не совпадают, то возвращается 0. Но, возвращаясь к функции sum, в обычной ситуации мы вполне себе в состоянии записать
double value=1+2L+3.0F+4.0;
просуммировав тем самым int, long, float и double. Подобного же поведения мы хотим добиться от функции sum.
То есть, при обработке значения boost::any, мы хотим сказать: «Эй, функция ‘Smth cast (any)’, если можешь, преобразуй any к типу Smth». А для того чтобы функция cast знала, какие типы она может привести к Smth нам потребуются списки типов. По сути, мы хотим задать список типов, которые можно безболезненно привести к требуемому типу.
Для описания списка преобразуемых типов не будем изобретать велосипед, а возьмем библиотеку boost::mpl и обнаружим там нужную нам концепцию последовательностей типов.
Теперь нам потребуется класс преобразователь типов в качестве замены boost::any_cast, а также класс диспетчер преобразователей типов, который
- Принимал бы тип, в который мы хотим выполнять преобразование ToType
- Принимал бы список типов CompatibleTypes из которых возможно преобразование в требуемый тип ToType
- На основании объекта boost::any возвращал бы нам подходящий преобразователь типа.
Класс преобразователь должен предоставлять нам функцию преобразования в требуемый тип, а также хранить указатель на объект type_info типа, из которого он может выполнить преобразование:
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;
};
Почему функция cast объявлена чисто виртуальной станет понятно чуть ниже. Теперь опишем класс диспетчер:
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);
};
Класс диспетчер параметризуется последоваетельностью допустимых типов CompatibleTypes и типом к которому выполняется преобразование ToType. В классе хранится контейнер указателей на Converter. Нам нужно, чтобы каждый Converter был привязан к одному из типов, перечисленных в CompatibleTypes. Для этого унаследуемся от Converter и реализуем функцию cast:
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));
}
};
Теперь у нас есть все, чтобы создать объекты преобразователи для всех типов в CompatibleTypes, а также реализовать функцию getConverter.
// функтор обертка над контейнером преобразователей
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));
}
Для прохода по списку типов CompatibleTypes мы воспользовались boost::mpl::for_each, который принимает объект функцию, применяемую к каждому типу в списке на этапе выполнения, для этого инстанируя объект каждого типа.
Реализация функции getConverter проста и очевидна:
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;
}
Вот и всё. Теперь мы можем написать нечто вроде
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->to(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);
}
Законченный пример кода можно посмотреть тут.
В итоге мы получили возможность работать с аргументами произвольных типов, относительно гибко настраивая правила их преобразования.
Автор: VnS