Вызов функции, соответствующей заданной строке

от автора

Привет!
Не знал, как поточнее назвать статью, но хотелось бы разобрать одну маленькую задачку, которая звучит следующим образом:

На вход подаётся отформатированная некоторым образом строка, в которой указаны имя функции, её аргументы и типы аргументов. Нужно иметь возможность вызвать соответствующий обработчик функции, корректно передав все аргументы.

Например, так ActionScript пытается вызвать функцию test с тремя аргументами str, false, 1.0(соответственно типы аргументов: String, Boolean, Number):

<invoke name="test" returntype="xml"><arguments><string>str</string><false/><number>1.0</number></arguments></invoke> 

Хотелось бы, чтобы со стороны C++ была вызвана соответствующая функция:

void test_handler(const std::wstring& str, bool flag, double n); 

Под катом — реализация с использованием нового стандарта и, для сравнения, реализация с использованием старого стандарта(и капельки boost-а).

Приступим. Для начала нужно как-то разобрать строку. Поскольку это не суть задачи, то, для разбора xml, будем использовать Boost.PropertyTree. Всё это спрячем в спомогательный класс InvokeParser, который будет хранить имя функции и массив пар тип аргумента-его значение:

InvokeParser

#include "boost/property_tree/ptree.hpp" #include "boost/property_tree/xml_parser.hpp"  namespace as3 {  class InvokeParser { public: 	using ArgsContainer = std::vector< 		std::pair< 			std::wstring, // Argument type 			std::wstring // Argument value 			>>;  public: 	InvokeParser() 		: invoke_name_() 		, arguments_() 	{ 	}  	bool parse(const std::wstring& str) 	{ 		using namespace boost; 		using namespace boost::property_tree;  		try 		{ 			std::wistringstream stream(str); 			wptree xml; 			read_xml(stream, xml);  			// Are 'invoke' tag attributes and 'arguments' tag exists? 			auto invoke_attribs = xml.get_child_optional(L"invoke.<xmlattr>"); 			auto arguments_xml = xml.get_child_optional(L"invoke.arguments"); 			if(!invoke_attribs || !arguments_xml) 				return false; 			// Is 'name' exists ? 			auto name = invoke_attribs->get_optional<std::wstring>(L"name"); 			if(!name) 				return false; 			invoke_name_ = *name;  			arguments_.reserve(arguments_xml->size()); 			for(const auto& arg_value_pair : *arguments_xml) 			{ 				std::wstring arg_type = arg_value_pair.first; 				std::wstring arg_value = arg_value_pair.second.get_value(L""); 				if((arg_type == L"true") || (arg_type == L"false")) 				{ 					arg_value = arg_type; 					arg_type = L"bool"; 				}  				arguments_.emplace_back(arg_type, arg_value); 			}  			return true; 		} 		catch(const boost::property_tree::xml_parser_error& /*parse_exc*/) 		{ 		} 		catch(...) 		{ 		} 		return false; 	}  	std::wstring function_name() const 	{ 		return invoke_name_; 	}  	size_t arguments_count() const 	{ 		return arguments_.size(); 	}  	const ArgsContainer& arguments() const 	{ 		return arguments_; 	}  private: 	std::wstring invoke_name_; 	ArgsContainer arguments_; }; } // as3 

Теперь напишем шаблонный класс Type, который будет иметь один параметр — некий C++-тип, в который нужно будет превратить строку, а также узнать соответствующее имя со стороны ActionScript. Например:

Type<short>::convert(L"20"); // Вернёт 20, тип short Type<short>::name(); // short для ActionScript это "number" 

Код шаблонного класса Type:

template<typename CppType> struct Type : 	std::enable_if< 		!std::is_array<CppType>::value, 		TypeHelper< 			typename std::decay<CppType>::type> 	>::type { };  template<typename CppType> struct Type<CppType*> { }; 

Здесь есть минимальный предосторожности для неосторожного пользователя данного шаблона, например, нельзя инстанцировать данный шаблон указателем на какой-то тип, так как мы не используем указатели(конкретно для ActionScript — указателей попросту нет). Конечно здесь не все предострожности, но их можно легко добавить.
Как видно, основную роботу выполняет другой шаблонный класс TypeHelper. TypeHelper инстанцируется «голым» типом. Например, для const std::wstring& получится std::wstring и т.д. Это делается с помощью decay. И делается это для того, чтобы убрать различия между функциями который имеют сигнатуры типа f(const std::wstring&) и f(std::wstring). Делаем также некоторую предосторожность для массивов, так как в этом случае послушный decay прекрасно справиться с роботой и мы получим не совсем то, что хотели.

Основной шаблон TypeHelper. Он будет служить для преобразования чисел. В нашем случае он будет прекрасно работать для Арифметических типов, хотя, по-хорошему, нужно-бы исключить все символьные типы(это сделать не сложно немножко модифицировав is_valid_type с помощью std::is_same).

TypeHelper

template<typename CppType> struct TypeHelper { private: 	enum { is_valid_type = std::is_arithmetic<CppType>::value }; public: 	typedef typename std::enable_if<is_valid_type, CppType>::type Type;  	static typename std::enable_if<is_valid_type, std::wstring>::type name() 	{ 		return L"number"; 	}  	// Convert AS3 number type from string to @CppType 	static Type convert(const std::wstring& str) 	{ 		double value = std::stod(str); 		return static_cast<Type>(value); 	} }; 

Как видно, имя всех числовых типов, в случае ActionScriptnumber. Для преобразования со строки, сначала аккуратно преобразовываем в double, а потом в нужный нам тип.
Также нам нужна другая обработка для типов bool, std::wstring и void:

Полные специализации TypeHelper для bool, std::wstring, void

template<> struct TypeHelper<bool> { 	typedef bool Type;  	static std::wstring name() 	{ 		return L"bool"; 	}  	static bool convert(const std::wstring& str) 	{ 		return (str == L"true"); 	} };  template<> struct TypeHelper<std::wstring> { 	typedef std::wstring Type;  	static std::wstring name() 	{ 		return L"string"; 	}  	static std::wstring convert(const std::wstring& str) 	{ 		return str; 	} };  template<> struct TypeHelper<void> { 	typedef void Type;  	static std::wstring name() 	{ 		return L"undefined"; 	}  	static void convert(const std::wstring& /*str*/) 	{ 	} }; 

Вот и всё, по-сути! Осталось аккуратно соединить всё вместе. Поскольку нужно иметь удобный механизм создания обработчиков соответствующих функций(хранение всех обработчиков в контейнере и т.д.), создадим интерфейс IFunction:

struct IFunction { 	virtual bool call(const InvokeParser& parser) = 0; 	 	virtual ~IFunction() 	{ 	} }; 

А вот классы-наследники уже будут знать, какую функцию нужно вызвать для конкретного случая. Снова шаблон:

template<typename ReturnType, typename... Args> struct Function : 	public IFunction { 	Function(const std::wstring& function_name, ReturnType (*f)(Args...)) 		: f_(f) 		, name_(function_name) 	{ 	} 	 	bool call(const InvokeParser& parser) 	{ 		if(name_ != parser.function_name()) 			return false; 		const auto ArgsCount = sizeof...(Args); 		if(ArgsCount != parser.arguments_count()) 			return false;  		auto indexes = typename generate_sequence<ArgsCount>::type(); 		auto args = parser.arguments();  		if(!validate_types(args, indexes)) 			return false;  		return call(args, indexes); 	}  private: 	template<int... S> 	bool validate_types(const InvokeParser::ArgsContainer& args, sequence<S...>) 	{ 		std::array<std::wstring, sizeof...(Args)> cpp_types = { Type<Args>::name()... }; 		std::array<std::wstring, sizeof...(S)> as3_types = { args[S].first... }; 		return (cpp_types == as3_types); 	}  	template<int... S> 	bool call(const InvokeParser::ArgsContainer& args, sequence<S...>) 	{ 		f_(Type<Args>::convert(args[S].second)...); 		return true; 	}  protected: 	std::function<ReturnType (Args...)> f_; 	std::wstring name_; };  template<typename ReturnType, typename... Args> std::shared_ptr<IFunction> make_function(const std::wstring& as3_function_name, ReturnType (*f)(Args...)) { 	return std::make_shared<Function<ReturnType, Args...>>(as3_function_name, f); } 

Сначала покажу как использовать:

void test_handler(const std::wstring& str, bool flag, double n) { 	std::wcout << L"test: " << str << L", " << std::boolalpha << flag << ", " << n << std::endl; }  int main() { 	as3::InvokeParser parser; 	std::wstring str = L"<invoke name=\"test\" returntype=\"xml\">" 		L"<arguments><string>str</string><false/><number>1.0</number></arguments>" 		L"</invoke>"; 	if(parser.parse(str)) 	{ 		auto function = as3::make_function(L"test", test_handler); 		function->call(parser); 	} } 

Перейдём к деталям. Во-первых, есть вспомогательная функция as3::make_function(), которая помогает не думать о типе передаваемого callback-а.
Во-вторых, для того, чтобы пройтись(более правильно — распаковать «паттерн», смотрим ссылку Parameter pack(ниже)) по пакету параметров(как перевести? Parameter pack) используются вспомогательные структуры sequence и generate_sequence:

template<size_t N, size_t... Sequence> struct generate_sequence : 	generate_sequence<N - 1, N - 1, Sequence...> { };  template<size_t...> struct sequence { };  template<int... Sequence> struct generate_sequence<0, Sequence...> { 	typedef sequence<Sequence...> type; }; 

Подсмотрено на stackoverflow.
В двух словах generate_sequence<N> генерирует пакет параметров 0, 1, 2, ... N - 1, который сохраняется(читать: «выводится компилятором») с помощью sequence. Это всё нужно для Pack expansion(буду переводить как «распаковка пакета»).
Например:
У нас есть пакет параметров typename... Args и функция f. Мы хотим вызвать f, передав каждое значение пакета некоторой функции, а результат этой функции уже передать f:

template<typename... Args> struct Test { 	template<int... S> 	static bool test(const std::vector<pair<int, float>>& args, sequence<S...>) 	{ 		f_(Type<Args>::convert(args[S].second)...); 		return true; 	}	 }; // где-то в коде test(args, typename generate_sequence<sizeof...(Args)>::type()) 

Вызов test для Args = <int, float> превратится в вызов:

f_(Type<int>::convert(args[0].second), Type<float>::convert(args[1].second)) 

Вот и вся магия!
Наша функция-член Function::validate_types создаёт два массива. Один массив содержит имена C++-типов в ActionScript-е, а другой — имена типов, со входящей строки. Если массивы не одинаковы — у нас некорректная сигнатура функции! И мы можем это диагностировать. Вот что значит мощь шаблонов!
А вспомогательная функция-член call(const InvokeParser::ArgsContainer& args, sequence<S...>) делает то, что в примере выше, только для нашего случая.
Всё — с помощью make_function() можно делать регистрацию обработчиков, поскольку мы имеем полиморфный тип IFunction, обработчики можно спокойно сохранять в массиве(да в чём угодно) и при поступлении очередной строки вызывать соответствующий обработчик.

Итак, а теперь код для старого стандарта, для максимального количества аргументов равного четырём с использованием boost::function_traits и совсем немножко mpl 🙂

InvokeParser

class InvokeParser { public: 	typedef std::vector<std::pair<std::wstring, std::wstring> > ArgsContainer; 	typedef ArgsContainer::value_type TypeValuePair;  public: 	InvokeParser() 		: invoke_name_() 		, arguments_() 	{ 	}  	bool parse(const std::wstring& str) 	{ 		using namespace boost; 		using namespace boost::property_tree;  		try 		{ 			std::wistringstream stream(str); 			wptree xml; 			read_xml(stream, xml);  			optional<wptree&> invoke_attribs = xml.get_child_optional(L"invoke.<xmlattr>"); 			optional<wptree&> arguments_xml = xml.get_child_optional(L"invoke.arguments"); 			if(!invoke_attribs || !arguments_xml) 				return false; 			optional<std::wstring> name = invoke_attribs->get_optional<std::wstring>(L"name"); 			if(!name) 				return false; 			invoke_name_ = *name;  			arguments_.reserve(arguments_xml->size()); 			for(wptree::const_iterator arg_value_pair = arguments_xml->begin(), end = arguments_xml->end(); arg_value_pair != end; ++arg_value_pair) 			{ 				std::wstring arg_type = arg_value_pair->first; 				std::wstring arg_value = arg_value_pair->second.get_value(L""); 				if((arg_type == L"true") || (arg_type == L"false")) 				{ 					arg_value = arg_type; 					arg_type = L"bool"; 				}  				arguments_.push_back(TypeValuePair(arg_type, arg_value)); 			}  			return true; 		} 		catch(const boost::property_tree::xml_parser_error& /*parse_exc*/) 		{ 		} 		catch(...) 		{ 		} 		return false; 	}  	std::wstring function_name() const 	{ 		return invoke_name_; 	}  	size_t arguments_count() const 	{ 		return arguments_.size(); 	}  	const ArgsContainer& arguments() const 	{ 		return arguments_; 	}  private: 	std::wstring invoke_name_; 	ArgsContainer arguments_; }; 

TypeHelper

template<typename CppType> struct TypeHelper { private: 	// Arithmetic types are http://en.cppreference.com/w/cpp/language/types. 	// Need to exclude 'Character types' from this list 	// (For 'Boolean type' this template has full specialization) 	typedef boost::mpl::and_< 		boost::is_arithmetic<CppType>, 		boost::mpl::not_<boost::is_same<CppType, char> >, 		boost::mpl::not_<boost::is_same<CppType, wchar_t> >, 		boost::mpl::not_<boost::is_same<CppType, unsigned char> >, 		boost::mpl::not_<boost::is_same<CppType, signed char> > > ValidCppType; public: 	// We can get C++ type name equivalent for AS3 "number" type only if 	// C++ type @CppType is @ValidCppType(see above) 	typedef typename boost::enable_if< 		ValidCppType, 		CppType>::type Type;  	// Get AS3 type name for given @CppType(see @ValidCppType) 	static 	typename boost::enable_if< 		ValidCppType, 		std::wstring>::type name() 	{ 		return L"number"; 	}  	// Convert AS3 number type from string to @CppType(see @ValidCppType) 	static 	Type convert(const std::wstring& str) 	{ 		double value = from_string<wchar_t, double>(str); 		// TODO: Use boost type cast 		return static_cast<Type>(value); 	} };  template<> struct TypeHelper<bool> { 	typedef bool Type;  	// AS3 type name for boolean type 	static std::wstring name() 	{ 		return L"bool"; 	}  	// Convert AS3 boolean value from string to our bool 	static bool convert(const std::wstring& str) 	{ 		return (str == L"true"); 	} };  template<> struct TypeHelper<std::wstring> { 	typedef std::wstring Type;  	static std::wstring name() 	{ 		return L"string"; 	}  	static std::wstring convert(const std::wstring& str) 	{ 		// Ok, do nothing 		return str; 	} };  template<> struct TypeHelper<void> { 	typedef void Type;  	// AS3 type name for void type.. 	static std::wstring name() 	{ 		return L"undefined"; 	}  	static void convert(const std::wstring& /*str*/) 	{ 		// Oops.. 		ASSERT_MESSAGE(false, "Can't convert from sring to void"); 	} };  // @TypeHelper provides implementation // only for "number" type(arithmetic, without characters type), bool, string and void. // For any other type @TypeHelper will be empty. // decay is used for removing cv-qualifier. But it's not what we want for arrays. // That's why using enable_if template<typename CppType> struct FlashType : 	boost::enable_if< 		boost::mpl::not_< 			boost::is_array<CppType> >, 		TypeHelper< 			typename std::tr1::decay<CppType>::type> 	>::type { };  // Partial specialization for pointers // There is no conversion from AS3 type to C++ pointer.. template<typename CppType> struct FlashType<CppType*> { 	// static assert }; 

То, чего не было ранее — FunctionCallerзаменяет распаковку….:

FunctionCaller

template<int N> struct FunctionCaller { 	template<typename Function> 	static bool call(Function /*f*/, const InvokeParser::ArgsContainer& /*args*/ #if defined(DEBUG) 		, const std::wstring& /*dbg_function_name*/ #endif 		) 	{ 		ASSERT_MESSAGE_AND_RETURN_VALUE( 			false, 			"Provide full FunctionCaller specialization for given arguments count", 			false); 	} };  template<> struct FunctionCaller<0> { 	template<typename Function> 	static bool call(Function f, const InvokeParser::ArgsContainer& /*args*/ #if defined(DEBUG) 		, const std::wstring& /*dbg_function_name*/ #endif 		) 	{ 		// Call function without args 		f(); 		return true; 	} };  template<> struct FunctionCaller<1> { 	template<typename Function> 	static bool call(Function f, const InvokeParser::ArgsContainer& args #if defined(DEBUG) 		, const std::wstring& dbg_function_name #endif 		) 	{ 		typedef FlashType<typename boost::function_traits<Function>::arg1_type> Arg1;		 		const InvokeParser::TypeValuePair& arg = args[0]; 		if(Arg1::name() != arg.first) 		{ #if defined(DEBUG) 			::OutputDebugStringW(Sprintf<wchar_t>( 				L"Function: \"%s\":\n" 				L"%s -> %s\n", 				dbg_function_name.c_str(), 				Arg1::name().c_str(), arg.first.c_str()).c_str()); #endif 			ASSERT_MESSAGE_AND_RETURN_VALUE(false, "Type mismatch", false); 		} 		// Call function with 1 arg 		f(Arg1::convert(arg.second)); 		return true; 	} };  template<> struct FunctionCaller<2> { 	template<typename Function> 	static bool call(Function f, const InvokeParser::ArgsContainer& args #if defined(DEBUG) 		, const std::wstring& dbg_function_name #endif 		) 	{ 		typedef FlashType<typename boost::function_traits<Function>::arg1_type> Arg1; 		typedef FlashType<typename boost::function_traits<Function>::arg2_type> Arg2; 		const InvokeParser::TypeValuePair& arg1 = args[0]; 		const InvokeParser::TypeValuePair& arg2 = args[1];  		if((Arg1::name() != arg1.first) || 			(Arg2::name() != arg2.first)) 		{ #if defined(DEBUG) 			::OutputDebugStringW(Sprintf<wchar_t>( 				L"Function: \"%s\":\n" 				L"%s -> %s\n" 				L"%s -> %s\n", 				dbg_function_name.c_str(), 				Arg1::name().c_str(), arg1.first.c_str(), 				Arg2::name().c_str(), arg2.first.c_str()).c_str()); #endif 			ASSERT_MESSAGE_AND_RETURN_VALUE(false, "Type mismatch", false); 		} 		// Call function with 2 args 		f(Arg1::convert(arg1.second), 			Arg2::convert(arg2.second)); 		return true; 	} };  template<> struct FunctionCaller<3> { 	template<typename Function> 	static bool call(Function f, const InvokeParser::ArgsContainer& args #if defined(DEBUG) 		, const std::wstring& dbg_function_name #endif 		) 	{ 		typedef FlashType<typename boost::function_traits<Function>::arg1_type> Arg1; 		typedef FlashType<typename boost::function_traits<Function>::arg2_type> Arg2; 		typedef FlashType<typename boost::function_traits<Function>::arg3_type> Arg3; 		const InvokeParser::TypeValuePair& arg1 = args[0]; 		const InvokeParser::TypeValuePair& arg2 = args[1]; 		const InvokeParser::TypeValuePair& arg3 = args[2];  		if((Arg1::name() != arg1.first) || 			(Arg2::name() != arg2.first) || 			(Arg3::name() != arg3.first)) 		{ #if defined(DEBUG) 			::OutputDebugStringW(Sprintf<wchar_t>( 				L"Function: \"%s\":\n" 				L"%s -> %s\n" 				L"%s -> %s\n" 				L"%s -> %s\n", 				dbg_function_name.c_str(), 				Arg1::name().c_str(), arg1.first.c_str(), 				Arg2::name().c_str(), arg2.first.c_str(), 				Arg3::name().c_str(), arg3.first.c_str()).c_str()); #endif 			ASSERT_MESSAGE_AND_RETURN_VALUE(false, "Type mismatch", false); 		} 		// Call function with 3 args 		f(Arg1::convert(arg1.second), 			Arg2::convert(arg2.second), 			Arg3::convert(arg3.second)); 		return true; 	} };  template<> struct FunctionCaller<4> { 	template<typename Function> 	static bool call(Function f, const InvokeParser::ArgsContainer& args #if defined(DEBUG) 		, const std::wstring& dbg_function_name #endif 		) 	{ 		typedef FlashType<typename boost::function_traits<Function>::arg1_type> Arg1; 		typedef FlashType<typename boost::function_traits<Function>::arg2_type> Arg2; 		typedef FlashType<typename boost::function_traits<Function>::arg3_type> Arg3; 		typedef FlashType<typename boost::function_traits<Function>::arg4_type> Arg4;  		const InvokeParser::TypeValuePair& arg1 = args[0]; 		const InvokeParser::TypeValuePair& arg2 = args[1]; 		const InvokeParser::TypeValuePair& arg3 = args[2]; 		const InvokeParser::TypeValuePair& arg4 = args[3];  		if((Arg1::name() != arg1.first) || 			(Arg2::name() != arg2.first) || 			(Arg3::name() != arg3.first) || 			(Arg4::name() != arg4.first)) 		{ #if defined(DEBUG) 			::OutputDebugStringW(Sprintf<wchar_t>( 				L"Function: \"%s\":\n" 				L"%s -> %s\n" 				L"%s -> %s\n" 				L"%s -> %s\n" 				L"%s -> %s\n", 				dbg_function_name.c_str(), 				Arg1::name().c_str(), arg1.first.c_str(), 				Arg2::name().c_str(), arg2.first.c_str(), 				Arg3::name().c_str(), arg3.first.c_str(), 				Arg4::name().c_str(), arg4.first.c_str()).c_str()); #endif 			ASSERT_MESSAGE_AND_RETURN_VALUE(false, "Type mismatch", false); 		} 		// Call function with 4 args 		f(Arg1::convert(arg1.second), 			Arg2::convert(arg2.second), 			Arg3::convert(arg3.second), 			Arg4::convert(arg4.second)); 		return true; 	} }; 

И сам Function:

Function

struct IFunction { 	virtual bool call(const InvokeParser& parser) = 0;  	virtual ~IFunction() 	{ 	} };  template<typename FunctionPointer> struct Function : 	public IFunction { 	Function(const std::wstring& function_name, FunctionPointer f) 		: f_(f) 		, name_(function_name) 	{ 	} 	 	bool call(const InvokeParser& parser) 	{ 		typedef typename boost::remove_pointer<FunctionPointer>::type FunctionType; 		enum { ArgsCount = boost::function_traits<FunctionType>::arity };  		ASSERT_MESSAGE_AND_RETURN_VALUE( 			name_ == parser.function_name(), 			"Incorrect function name", 			false);  		ASSERT_MESSAGE_AND_RETURN_VALUE( 			ArgsCount == parser.arguments_count(), 			"Incorrect function arguments count", 			false);  		return FunctionCaller<ArgsCount>::template call<FunctionType>( 			f_, parser.arguments() #if defined(DEBUG) 			, name_ #endif 			); 	}  protected: 	FunctionPointer f_; 	std::wstring name_; };  template<typename FunctionPointer> IFunction* CreateFunction(const std::wstring& name, FunctionPointer f) { 	return new Function<FunctionPointer>(name, f); } 

Спасибо за внимание!

ссылка на оригинал статьи http://habrahabr.ru/post/223865/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *