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

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

Например, так ActionScript пытается вызвать функцию test с тремя аргументами str, false, 1.0(соответственно типы аргументов: String, Boolean, Number): str1.0 Хотелось бы, чтобы со стороны 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.»); 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(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:: convert (L»20»); // Вернёт 20, тип short Type:: name (); // short для ActionScript это «number» Код шаблонного класса Type: template struct Type: std: enable_if< !std::is_array:: value, TypeHelper< typename std::decay:: type> >:: type { };

template struct Type { }; Здесь есть минимальный предосторожности для неосторожного пользователя данного шаблона, например, нельзя инстанцировать данный шаблон указателем на какой-то тип, так как мы не используем указатели (конкретно для 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 struct TypeHelper { private: enum { is_valid_type = std: is_arithmetic:: value }; public: typedef typename std: enable_if:: type Type;

static typename std: enable_if:: 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(value); } }; Как видно, имя всех числовых типов, в случае ActionScript — number. Для преобразования со строки, сначала аккуратно преобразовываем в double, а потом в нужный нам тип.Также нам нужна другая обработка для типов bool, std: wstring и void: Полные специализации TypeHelper для bool, std: wstring, void template<> struct TypeHelper { typedef bool Type;

static std: wstring name () { return L«bool»; }

static bool convert (const std: wstring& str) { return (str == L«true»); } };

template<> struct TypeHelper { typedef std: wstring Type;

static std: wstring name () { return L«string»; }

static std: wstring convert (const std: wstring& str) { return str; } };

template<> struct TypeHelper { 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 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:: type (); auto args = parser.arguments ();

if (! validate_types (args, indexes)) return false;

return call (args, indexes); }

private: template bool validate_types (const InvokeParser: ArgsContainer& args, sequence) { std: array cpp_types = { Type:: name ()… }; std: array as3_types = { args[S].first… }; return (cpp_types == as3_types); }

template bool call (const InvokeParser: ArgsContainer& args, sequence) { f_(Type:: convert (args[S].second)…); return true; }

protected: std: function f_; std: wstring name_; };

template std: shared_ptr make_function (const std: wstring& as3_function_name, ReturnType (*f)(Args…)) { return std: make_shared>(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»» L»str1.0» L»»; 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 struct generate_sequence: generate_sequence { };

template struct sequence { };

template struct generate_sequence<0, Sequence...> { typedef sequence type; }; Подсмотрено на stackoverflow.В двух словах generate_sequence генерирует пакет параметров 0, 1, 2, … N — 1, который сохраняется (читать: «выводится компилятором») с помощью sequence. Это всё нужно для Pack expansion (буду переводить как «распаковка пакета»).Например: У нас есть пакет параметров typename… Args и функция f. Мы хотим вызвать f, передав каждое значение пакета некоторой функции, а результат этой функции уже передать f: template struct Test { template static bool test (const std: vector>& args, sequence) { f_(Type:: convert (args[S].second)…); return true; } }; // где-то в коде test (args, typename generate_sequence:: type ()) Вызов test для Args = превратится в вызов: f_(Type:: convert (args[0].second), Type:: convert (args[1].second)) Вот и вся магия! Наша функция-член Function: validate_types создаёт два массива. Один массив содержит имена C++-типов в ActionScript-е, а другой — имена типов, со входящей строки. Если массивы не одинаковы — у нас некорректная сигнатура функции! И мы можем это диагностировать. Вот что значит мощь шаблонов! А вспомогательная функция-член call (const InvokeParser: ArgsContainer& args, sequence) делает то, что в примере выше, только для нашего случая.Всё — с помощью make_function () можно делать регистрацию обработчиков, поскольку мы имеем полиморфный тип IFunction, обработчики можно спокойно сохранять в массиве (да в чём угодно) и при поступлении очередной строки вызывать соответствующий обработчик.Итак, а теперь код для старого стандарта, для максимального количества аргументов равного четырём с использованием boost: function_traits и совсем немножко mpl:)

InvokeParser class InvokeParser { public: typedef std: vector > 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 invoke_attribs = xml.get_child_optional (L«invoke.»); optional arguments_xml = xml.get_child_optional (L«invoke.arguments»); if (! invoke_attribs || ! arguments_xml) return false; optional name = invoke_attribs→get_optional(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 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, boost: mpl: not_ >, boost: mpl: not_ >, boost: mpl: not_ >, boost: mpl: not_ > > 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(str); // TODO: Use boost type cast return static_cast(value); } };

template<> struct TypeHelper { 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 { 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 { 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 struct FlashType: boost: enable_if< boost::mpl::not_< boost::is_array >, TypeHelper< typename std::tr1::decay:: type> >:: type { };

// Partial specialization for pointers // There is no conversion from AS3 type to C++ pointer… template struct FlashType { // static assert }; То, чего не было ранее — FunctionCaller — заменяет распаковку…:

FunctionCaller template struct FunctionCaller { template 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 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 static bool call (Function f, const InvokeParser: ArgsContainer& args #if defined (DEBUG) , const std: wstring& dbg_function_name #endif ) { typedef FlashType:: arg1_type> Arg1; const InvokeParser: TypeValuePair& arg = args[0]; if (Arg1:: name () != arg.first) { #if defined (DEBUG) :: OutputDebugStringW (Sprintf( 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 static bool call (Function f, const InvokeParser: ArgsContainer& args #if defined (DEBUG) , const std: wstring& dbg_function_name #endif ) { typedef FlashType:: arg1_type> Arg1; typedef FlashType:: 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( 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 static bool call (Function f, const InvokeParser: ArgsContainer& args #if defined (DEBUG) , const std: wstring& dbg_function_name #endif ) { typedef FlashType:: arg1_type> Arg1; typedef FlashType:: arg2_type> Arg2; typedef FlashType:: 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( 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 static bool call (Function f, const InvokeParser: ArgsContainer& args #if defined (DEBUG) , const std: wstring& dbg_function_name #endif ) { typedef FlashType:: arg1_type> Arg1; typedef FlashType:: arg2_type> Arg2; typedef FlashType:: arg3_type> Arg3; typedef FlashType:: 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( 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 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:: type FunctionType; enum { ArgsCount = boost: function_traits:: 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:: template call( f_, parser.arguments () #if defined (DEBUG) , name_ #endif ); }

protected: FunctionPointer f_; std: wstring name_; };

template IFunction* CreateFunction (const std: wstring& name, FunctionPointer f) { return new Function(name, f); } Спасибо за внимание!

© Habrahabr.ru