Пользовательские функции в Си
Итак, зачем нужны пользовательские функции? Пользовательские функции нужны для того, чтобы программистам было проще писать программы.
Помните, мы говорили о парадигмах программирования, а точнее о структурном программировании. Основной идеей там было то, что любую программу можно написать используя только три основных конструкции: следование, условие и цикл. Теперь к этим конструкциям мы добавим ещё одну – «подпрограммы» – и получим новую парадигму процедурное программирование» .
Отличие лишь в том, что отдельные кусочки нашей основной программы (в частности, повторяющиеся) мы будем записывать в виде отдельных функций (подпрограмм, процедур) и по мере необходимости их вызывать. По сути, программа теперь будет описывать взаимодействие различных функций.
В принципе, мы уже используем эту парадигму. Если вам пока ещё не совсем ясно, почему это проще, то просто представьте, что вместо того чтобы вызвать функцию exp(x) из заголовочного файла math.h вам каждый раз необходимо было бы описывать подробно, как вычислить значение этой функции.
Итак, в этом уроке мы подробно обсудим то, как функции устроены изнутри. А также научимся создавать свои собственные пользовательские функции.
Как устроены функции
Вспомним информацию с первого урока. Все функции, в том числе и те, которые пишет пользователь, устроены сходным образом. У них имеется две основных составных части: заголовок функции и тело функции.
int main(void)< // заголовок функции // в фигурных скобках записано тело функции >
С телом функции всё ясно: там описывается алгоритм работы функции. Давайте разберёмся с заголовком. Он состоит из трёх обязательных частей:
- тип возвращаемого значения;
- имя функции;
- аргументы функции.
Сначала записывается тип возвращаемого значения, например, int , как в функции main . Если функция не должна возвращать никакое значение в программу, то на этом месте пишется ключевое слово void . Казалось бы, что раз функция ничего не возвращает, то и не нужно ничего писать. Раньше, кстати, в языке Си так и было сделано, но потом для единообразия всё-таки добавили. Сейчас современные компиляторы будут выдавать предупреждения/ошибки, если вы не укажете тип возвращаемого значения.
В некоторых языках программирования функции, которые не возвращают никакого значения, называют процедурами (например, pascal). Более того, для создания функций и процедур предусмотрен различный синтаксис. В языке Си такой дискриминации нет.
После типа возвращаемого значения записывается имя функции. Ну а уж после имени указываются типы и количество аргументов, которые передаются в функцию.
Давайте посмотрим на заголовки уже знакомых нам функций.
// функция с именем srand, принимающая целое число, ничего не возвращает void srand(int) //функция с именем sqrt, принимающая вещественное число типа float, возвращает вещественное число типа float float sqrt(float) //функция с именем rand, которая не принимает аргументов, возвращает целое число int rand(void) //функция с именем pow, принимающая два аргумента типа double, возвращает вещественное число типа double double pow(double, double)
Как создать свою функцию
Для того чтобы создать свою функцию, необходимо её полностью описать. Тут действует общее правило: прежде чем использовать – объяви и опиши, как должно работать. Для этого вернёмся к схеме структуры программы на языке Си, которая у нас была в самом первом уроке. Отметим на ней те места, где можно описывать функции.
Рис.1 Уточнение структуры программы. Объявление функций.
Как видите, имеется аж два места, где это можно сделать.
Давайте посмотрим на пример, который иллюстрируют создание пользовательской функции вычисления максимального из двух чисел.
#include // объявляем пользовательскую функцию с именем max_num // вход: два целочисленных параметра с именами a и b // выход: максимальное из двух аргументов int max_num(int a, int b) < int max = b; if (a >b) max = a; return max; > //основная программа int main(void)
Давайте я подробно опишу, как будет работать эта программа. Выполняется тело функции main . Создются целые переменные x , y и m . В переменные x и y считываются данные с клавиатуры. Допустим мы ввели 3 5 , тогда x = 3 , y = 5 . Это вам всё и так должно быть понятно. Теперь следующая строчка
m = max_num(x,y);
Переменной m надо присвоить то, что находится справа от знака = . Там у нас указано имя функции, которую мы создали сами. Компьютер ищет объявление и описание этой функции. Оно находится выше. Согласно этому объявлению данная функция должна принять два целочисленных значения. В нашем случае это значения, записанные в переменных x и y . Т.е. числа 3 и 5 . Обратите внимание, что в функцию передаются не сами переменные x и y , а только значения (два числа), которые в них хранятся. То, что на самом деле передаётся в функцию при её вызове в программе, называется фактическими параметрами функции.
Теперь начинает выполняться функция max_num . Первым делом для каждого параметра, описанного в заголовке функции, создается отдельная временная переменная. В нашем случае создаются две целочисленных переменных с именами a и b . Этим переменным присваиваются значения фактических параметров. Сами же параметры, описанные в заголовке функции, называются формальными параметрами. Итак, формальным параметрам a и b присваиваются значения фактических параметров 3 и 5 соответственно. Теперь a = 3 , b = 5 . Дальше внутри функции мы можем работать с этими переменными так, как будто они обычные переменные.
Создаётся целочисленная переменная с именем max , ей присваивается значение b . Дальше проверяется условие a > b . Если оно истинно, то значение в переменной max следует заменить на a .
Далее следует оператор return , который возвращает в вызывающую программу (функцию main ) значение, записанное в переменной max , т.е. 5 . После чего переменные a , b и max удаляются из памяти. А мы возвращаемся к строке
m = max_num(x,y);
Функция max_num вернула значение 5 , значит теперь справа от знака = записано 5 . Это значение записывается в переменную m. Дальше на экран выводится строчка, и программа завершается.
Внимательно прочитайте последние 4 абазаца ещё раз, чтобы до конца уяснить, как работает программа.
А я пока расскажу, зачем нужен нижний блок описания функций. Представьте себе, что в вашей программе вы написали 20 небольших функций. И все они описаны перед функцией main . Не очень-то удобно добираться до основной программы так долго. Чтобы решить эту проблему, функции можно описывать в нижнем блоке.
Но просто так перенести туда полностью код функции не удастся, т.к. тогда нарушится правило: прежде чем что-то использовать, необходимо это объявить. Чтобы избежать подобной проблемы, необходимо использовать прототип функции.
Прототип функции полностью повторяет заголовок функции, после которого стоит ; . Указав прототип в верхнем блоке, в нижнем мы уже можем полностью описать функцию. Для примера выше это могло бы выглядеть так:
#include int max_num(int, int); int main(void) < int x =0, y = 0; int m = 0; scanf("%d %d", &x, &y); m = max_num(x,y); printf("max(%d,%d) = %d\n",x,y,m); return 0; >int max_num(int a, int b) < int max = b; if (a >b) max = a; return max; >
Всё очень просто. Обратите внимание, что у прототипа функции можно не указывать имена формальных параметров, достаточно просто указать их типы. В примере выше я именно так и сделал.
Сохрани в закладки или поддержи проект.
Практика
Решите предложенные задачи:
Для удобства работы сразу переходите в полноэкранный режим
Дополнительные материалы
- пока нет
Функции
Функция определяет действия, которые выполняет программа. Функции позволяют выделить набор инструкций и назначить ему имя. А затем многократно по присвоенному имени вызывать в различных частях программы. По сути функция — это именованный блок кода.
Формальное определение функции выглядит следующим образом:
тип имя_функции(параметры)
Первая строка представляет заголовок функции. Вначале указывается возвращаемый тип функции. Если функция не возвращает никакого значения, то используется тип void .
Затем идет имя функции, которое представляет произвольный идентификатор. К именованию функции применяются те же правила, что и к именованию переменных.
После имени функции в скобках идет перечисление параметров. Функция может не иметь параметров, в этом случае указываются пустые скобки.
После заголовка функции в фигурных скобках идет тело функции, которое содержит выполняемые инструкции.
Для возвращения результата функция применяет оператор return . Если функция имеет в качестве возвращаемого типа любой тип, кроме void, то она должна обязательно с помощью оператора return возвращать какое-либо значение.
Например, определение функции main, которая должна быть в любой программе на языке C++ и с которой начинается ее выполнение:
int main()
Возвращаемым типом функции является тип int , поэтому функция должна использовать оператор return и возвращать какое-либо значение, которое соответствует типу int. Возвращаемое значение ставится после оператора return.
Стоит отметить, что С++ позволяет не использовать оператор return в функции main:
int main()
Но если функция имеет тип void , то ей не надо ничего возвращать. Например, мы могли бы определить следующую функцию, которая просто выводит некоторый текст на консоль:
void hello()
Выполнение функции
Когда запускается программа на языке C++, то запускается функция main. Никакие другие функции, определенные в программе, автоматически не выполняются. Для выполнения функции ее необходимо вызвать. Вызов функции осуществляется в форме:
имя_функции(аргументы);
После имени функции указываются скобки, в которых перечисляются аргументы — значения для параметров функции.
Например, определим и выполним простейшую функцию:
#include void hello() < std::cout int main()
Здесь определена функция hello, которая вызывается в функции main два раза. В этом и заключается преимущество функций: мы можем вынести некоторые общие действия в отдельную функцию и затем вызывать многократно в различных местах программы. В итоге программа два раза выведет строку «hello».
hello hello
Объявление функции
При использовании функций стоит учитывать, что компилятор должен знать о функции до ее вызова. Поэтому вызов функции должен происходить после ее определения, как в случае выше. В некоторых языках это не имеет значение, но в языке C++ это играет большую роль. И если, к примеру, мы сначала вызовем, а потом определим функцию, то мы получим ошибку на этапе компиляции, как в следующем случае:
#include int main() < hello(); hello(); >void hello()
В этом случае перед вызовом функции надо ее дополнительно объявить. Объявление функции еще называют прототипом. Формальное объявление выглядит следующим образом:
тип имя_функции(параметры);
Фактически это заголовок функции. То есть для функции hello объявление будет выглядеть следующим образом:
void hello();
Используем объявление функции:
#include void hello(); int main() < hello(); hello(); >void hello()
В данном случае несмотря на то, что определение функции идет после ее вызова, но так как функция уже объявлена до ее вызова, то компилятор будет знать о функции hello, и никаких проблем в работе программы не возникнет.
Как вызвать функцию ?
Как вызвать функцию
Хочу создать хранилище , у базового класса есть функция virtual void parametri() < cout << ".
Как вызвать функцию ?
Вот у меня есть вот такая функция как её вызвать ? void CAimbot::pSilent(CUserCmd* pCmd)
Как вызвать функцию?
У меня допустим есть уже готовые функции с именами от а0 до а1000 а мне нужно чтобы прога исходя.
174 / 134 / 105
Регистрация: 14.04.2016
Сообщений: 719
В первом случае выводится адрес функции в памяти компьютера. Любая функция размещается в памяти, как и переменные. Вы получаете её адрес в шестнадцатеричной системе счисления.
Вы должны передать в функцию аргументы, так как определили аргумент (int).
299 / 208 / 174
Регистрация: 11.05.2016
Сообщений: 655
Nightingale81, std::cout в скобках указывайте число или переменную типа int
7595 / 6418 / 2924
Регистрация: 14.04.2014
Сообщений: 27,939
В том, что x не определён у тебя. И вообще не нужен.
1 2 3 4 5 6
int number() { return 3; } std::cout number();
Регистрация: 22.09.2015
Сообщений: 161
Nightingale81, для вызова функции вы должны ставить пустые скобки, если функция не принимает ни каких параметров. если она принимает параметры, то в круглых скобках вы должны написать значение параметров. в вашем случае это будет так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
#include int number(int x) { x = 3; return x; } int main() { //cout std::cout number(10) "\n"; system("pause"); }
У вас выводиться адрес функции в памяти компьютера, в данном случае(если писать без круглы скобок) вы манипулируете указателем на функцию а не вызываете ее.
Регистрация: 09.06.2016
Сообщений: 241
Извините, но ничего не понял. Откуда number(10) Почему?
Хорошо, попробую по другому объяснить.
Надо создать функцию, в которой выполняется какое либо простое арифметическое действие. И результат этого действия (например) прибавить к другому числу в функции main.
Вывести на экран сумму чисел «20» и содержимого переменной «z» функции number (как в коде ниже):
1 2 3 4 5 6 7 8 9 10 11 12 13 14
#include int number(int x, int y, int z) { x = 3, y = 7; z = x + y; return z; } int main() { std::cout 20 + number(z) "\n"; system("pause"); }
Однако идентификатор «z» не определён.
Почему он не определён, когда его явно определили как аргумент функции number.
Так что же тут не так?
Как его вызвать??
299 / 208 / 174
Регистрация: 11.05.2016
Сообщений: 655
Сообщение от Nightingale81
1 2 3 4 5
int number(int x, int y, int z) { x = 3, y = 7; z = x + y; return z; }
я так понял Вы хотите это:
1 2 3 4 5 6 7 8 9 10 11
int number(int x, int y) { int z; z = x + y; return z; } int main() { std::cout 20 + number(3, 7); }
1 2 3 4 5 6
int main() { int xxx = 3; int yyy = 7; std::cout 20 + number(xxx, yyy); }
Регистрация: 22.09.2015
Сообщений: 161
Сообщение от Nightingale81
Однако идентификатор «z» не определён.
Почему он не определён, когда его явно определили как аргумент функции number
Потому что вы его не определили.
Вам требуется сначла определить переменную, задать ей значение, а потом уже вызывать функцию.
Сообщение от Nightingale81
Откуда number(10) Почему?
Я от балды написал 10, можно было и 35 написать, функция принимает аргумент, его надо передать обязательно.
Так как ваша функция принимала аргумент целочисленного типа, я и написал в аргументе целое число.
Сообщение от Nightingale81
Так что же тут не так?
Как его вызвать??
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
#include // чтобы функция изменила передаваемую переменную ее надо передавать по ссылке //знак & лзначает что мы передаем ссылку на переменную void number(int &z) { int x = 3; int y = 7; z = z + x + y; } int main() { //определяем новую переменную // = ; //int - это челочисленный тип int z = 0; //вызываем функцию //(); number(z); std::cout 20 + z "\n"; system("pause"); }
174 / 134 / 105
Регистрация: 14.04.2016
Сообщений: 719
Nightingale81,
int number(int x, int y) //Функция X и Y - аргументы
Аргументов 2, значит вам необходимо при вызове функции передавать два значения.
number(10, 20)//Передали 10 и 20
Смотрите что происходит дальше:
Вам не нужно приравнивать в функции переменные, так как они автоматически приравниваются к переданным значениям. В нашем случае в теле функции происходит следующее:
1 2 3 4 5
int number(int x, int y) { int x = 10; //Проделывается уже в скобках int y = 20; //Проделывается уже в скобках }
Сколько параметров столько и аргументов необходимо передавать. Есть способ установки аргументов по умолчанию, но вы об этом узнаете позже.
192 / 128 / 52
Регистрация: 19.01.2010
Сообщений: 518
Nightingale81,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
#include // x,y,z - формальные параметры функции. Они так будут называться только внутри этой функции int number(int x, int y, int z) { x = 3, y = 7; z = x + y; // бессмысленно. x,y,z в функцию попадают извне. return z; } int main() { // z не определена в области видимости функции int main(). Объявите "int z" перед передачей z в функцию // И вообще должно быть 3 параметра на входе, а не 1. Вы сами описали функцию, как принимающую 3 параметра на входе std::cout 20 + number(z) "\n"; system("pause"); }
4055 / 3308 / 924
Регистрация: 25.03.2012
Сообщений: 12,447
Записей в блоге: 1
Nightingale81, может если не учить С++ по книжке «С++ для чайников», а главное не додумывать за создателей языка как должны выглядеть функции, то всё получится!
Вызов функции, соответствующей заданной строке
На вход подаётся отформатированная некоторым образом строка, в которой указаны имя функции, её аргументы и типы аргументов. Нужно иметь возможность вызвать соответствующий обработчик функции, корректно передав все аргументы.
Например, так ActionScript пытается вызвать функцию test с тремя аргументами str, false, 1.0(соответственно типы аргументов: String, Boolean, Number):
str 1.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::arraycpp_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 int main() < as3::InvokeParser parser; std::wstring str = L"" L"str 1.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 < typedef sequencetype; >;
Подсмотрено на stackoverflow.
В двух словах generate_sequence генерирует пакет параметров 0, 1, 2, . N — 1 , который сохраняется(читать: «выводится компилятором») с помощью sequence . Это всё нужно для Pack expansion(буду переводить как «распаковка пакета»).
Например:
У нас есть пакет параметров typename. Args и функция f . Мы хотим вызвать f , передав каждое значение пакета некоторой функции, а результат этой функции уже передать f :
template struct Test < templatestatic 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); optionalinvoke_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 < templatestatic 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 < templatestatic 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 < templatestatic 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 < templatestatic 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 < templatestatic 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 < templatestatic 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
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); >
Спасибо за внимание!
UPD: забыл добавить — в самом новом, грядущем, 14м стандарте есть(будет) std::make_index_sequence, который делает всё то же, что и generate_sequence .