Вызов функции, соответствующей заданной строке23.05.2014 02:48
Привет! Не знал, как поточнее назвать статью, но хотелось бы разобрать одну маленькую задачку, которая звучит следующим образом:
На вход подаётся отформатированная некоторым образом строка, в которой указаны имя функции, её аргументы и типы аргументов. Нужно иметь возможность вызвать соответствующий обработчик функции, корректно передав все аргументы.
Например, так 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