Разработка сайта для Вашего бизнеса. Веб дизайн. Дизайн логотипа, фирменного стиля, рекламная фотография . Комплексный рекламный креатив.

Ralex. We do the work.
На рынке с 1999го года. Средняя ценовая категория. Ориентация на эффективность решений.
Ознакомтесь с нашим портфолио
Узнайте больше о услугах
Свяжитесь с нами:
E-mail: [email protected]
Tel: (044) 587 - 84 - 78
Custom web design & дизайн и разработка сайта "под ключ"
Креативный, эффективный дизайн. Система управления сайтом (СУС).
Custom flexible разработка систем электронной коммерции
Система e-commerce разрабатывается под индивидуальные потребности. Гибкая функциональность.
Search Engine Optimzation & оптимизация под поисковые системы (SEO)
Постоянная оптимизация и мониторинг сайта в поисковых системах. Достигаем результата быстро и эффективно
Custom logo design & дизайн логотипа и фирменного стиля
Многолетний опыт. Огромное портфолио. Уникальное предложение и цена.
профессиональная рекламная фотография
креативно, смело, качественно
Custom logo design & рекламный креатив. дизайн рекламы
Многолетний опыт. Огромное портфолио. Уникальное предложение и цена.

Метапрограмування. Шаблони виразів (expression templates). Частина 2

  1. C ++ Expression Templates
  2. © Клаус Крефт і Анжеліка Лангер
  3. Лістинг 9: Об'єктно-орієнтована реалізація інтерпретатора для арифметичних виразів
  4. Лістинг 10: Використання інтерпретатора для арифметичних виразів
  5. Лістинг 11: Шаблонна реалізація інтерпретатора арифметичних виразів
  6. створюють функції
  7. Лістинг 12: Створюють функції для об'єктів виразів
  8. Лістинг 13: Використання шаблонного інтерпретатора для арифметичних виразів
  9. оцінка
  10. Подальше вдосконалення рішення на основі шаблонів
  11. Лістинг 14: створити функція для вираження суми через перевантажений operator +
  12. Лістинг 15: Використання шаблонного інтерпретатора для арифметичних виразів
  13. властивості traits
  14. Лістинг 16: Властивості виразів
  15. Лістінг 17: Використання властівостей виразів
  16. Лістинг 18: Допоміжна функція eval ()
  17. Повторне використання об'єктів виразів
  18. Лістинг 19: Шаблон вираження з повторним використанням
  19. Лістинг 20: Обчислення розподілу Гаусса
  20. Лістинг 21: Числові функції як шаблони нетермінальних виразів
  21. Шаблони виразів (expression templates). Частина 1

Наша взаимовыгодная связь https://banwar.org/

статті »Метапрограмування. Шаблони виразів (expression templates). Частина 2

C ++ Expression Templates

An Introduction to the Principles of Expression Templates

Попередня версія статті, опублікованій в C / C ++ Users Journal, березень 2003 р

© Клаус Крефт і Анжеліка Лангер

Інший шаблон виразів - арифметичні вираження

Давайте просунемося ще один на один крок далі і розглянемо реальну складову структуру: арифметичні вирази, що складаються з унарних і бінарних арифметичних операторів, змінних і констант. Саме для цього випадку в книзі GOF є патерн Interpreter.

Патерн Interpreter передбачає подання мови у вигляді абстрактного синтаксичного дерева і інтерпретатор, який використовує це синтаксичне дерево для інтерпретації мовних конструкцій. Це окремий випадок паттерна Composite. Ставлення "частина-ціле" патерну Composite відповідає відношенню вираження і подвираженія в паттерне Interpreter (інтерпретатор) .

  • Leaf є термінальним виразом.
  • Composite є нетермінальним виразом.
  • Обчислення компонентів є інтерпретацією синтаксичного дерева і його виразів.

Синтаксичне дерево представляється такими арифметичними виразами як (a + 1) * c або log (abs (х-N)). Є два типи терміналів: числові літерали і числові змінні. Літерали мають константне значення, в той час як значення змінних можуть змінюватися між інтерпретаціями виразу. Нетерміналом є унарні або бінарні вираження, що складаються з одного або двох подвираженій. Вирази мають різну семантику, таку як +, -, *, /, ++, -, exp, log, sqrt.

Давайте візьмемо конкретний приклад вираження, скажімо (x + 2) * 3. Складова структура, тобто синтаксичне дерево для цього виразу буде виглядати наступним чином:

Складова структура, тобто синтаксичне дерево для цього виразу буде виглядати наступним чином:

Мал. 7. Приклад синтаксичного дерева для арифметичного виразу

Класичний об'єктно-орієнтований підхід для реалізації паттерна Interpreter, як запропоновано в книзі GOF, буде включати в себе наступні класи:

Мал. 8. Uml-діаграма класів об'єктно-орієнтованої реалізації інтерпретатора арифметичних виразів

Відповідний вихідний код реалізації показаний в лістингу 9. Базовий клас для UnaryExpr реалізується за аналогією з класом BinaryExpr і все конкретні унарні і бінарні вираження наслідують приклад класу Sum.

Лістинг 9: Об'єктно-орієнтована реалізація інтерпретатора для арифметичних виразів

class AbstractExpr {public: virtual double eval () const = 0; }; class TerminalExpr: public AbstractExpr {}; class NonTerminalExpr: public AbstractExpr {}; class Literal: public TerminalExpr {public: Literal (double v): _val (v) {} double eval () const {return _val; } Private: const double _val; }; class Variable: public TerminalExpr {public: Variable (double & v): _val (v) {} double eval () const {return _val; } Private: double & _val; }; class BinaryExpr: public NonTerminalExpr {protected: BinaryExpr (const AbstractExpr * e1, const AbstractExpr * e2): _expr1 (e1), _ expr2 (e2) {} virtual ~ BinaryExpr () {delete const_cast <AbstractExpr *> (_ expr1); delete const_cast <AbstractExpr *> (_ expr2); } Const AbstractExpr * _expr1; const AbstractExpr * _expr2; }; class Sum: public BinaryExpr {public: Sum (const AbstractExpr * e1, const AbstractExpr * e2): BinExpr (e1, e2) {} double eval () const {return _expr1-> eval () + _expr2-> eval (); }}; ...

Лістинг 10 показує, як буде використовуватися інтерпретатор для обчислення виразу (x + 2) * 3.

Лістинг 10: Використання інтерпретатора для арифметичних виразів

void someFunction (double x) {Product expr (new Sum (new Variable (x), new Literal (2)), new Literal (3)); cout << expr.eval () << endl; }

Спочатку створюється об'єкт вираження expr, який представляє вираз (x + 2) * 3 і потім обчислюється об'єкт вираження. Звичайно, це вкрай неефективний спосіб обчислення результату такого примітивного вираження, як (x + 2) * 3. зачекайте; тепер ми перетворимо об'єктно-орієнтований підхід до вирішення на основі шаблону.

Як і раніше, в разі скалярного твори ми усунемо всі абстрактні базові класи, так як шаблонні рішення засновані на спільності імен, а не успадкування. З цієї причини, ми не потребуємо базових класах. Замість подання всіх термінальних і нетермінальних виразів за допомогою автономних класів, ми будемо використовувати функцію оцінки з ім'ям eval ().

Потім ми висловимо всі нетермінальні вираження, такі як сума або твір, через класи, породжені від шаблонів класів UnaryExpr і BinaryExpr, кожен з яких параметрізіруется структурної інформацією. Ці шаблони класів братимуть типи їх подвираженій як "типові" шаблонні аргументи. Крім того, ми параметрізіруем шаблони класів виразів типом операції, які вони представляють, тобто, фактична операція (+, -, *, /, ++, -, abs, exp, log) буде надана як об'єкт функції і її тип буде одним з шаблонних аргументів в шаблоні класу вирази.

Термінальні вирази будуть реалізовані як звичайні (нешаблонні) класи як при об'єктно-орієнтованому підході.

Замість рекурсії на етапі виконання ми знову будемо використовувати рекурсію на етапі компіляції: ми замінимо рекурсивний виклик віртуальної обчислювальної функції на рекурсивне створення екземплярів шаблонів класів виразів.

На малюнку 9 нижче показані класи на основі шаблонного рішення:

Мал. 9. UML-діаграма класів шаблонної реалізації інтерпретатора арифметичних виразів

Вихідний код реалізації приведений в лістингу 11.

Лістинг 11: Шаблонна реалізація інтерпретатора арифметичних виразів

class Literal {public: Literal (const double v): _val (v) {} double eval () const {return _val; } Private: const double _val; }; class Variable {public: Variable (double & v): _val (v) {} double eval () const {return _val; } Private: double & _val; }; template <class ExprT1, class ExprT2, class BinOp> class BinaryExpr {public: BinaryExpr (ExprT1 e1, ExprT2 e2, BinOp op = BinOp ()): _expr1 (e1), _ expr2 (e2), _ op (op) {} double eval () const {return _op (_expr1.eval (), _ expr2.eval ()); } Private: ExprT1 _expr1; ExprT2 _expr2; BinOp _op; }; ...

Шаблон класу для UnaryExpr реалізується за аналогією з класом BinaryExpr. Як операцій ми можемо використовувати зумовлені в STL типи об'єктів функцій плюс, мінус, множення і ділення, і т.д., а також можемо визначити наші власні типи об'єктів функцій в разі потреби. Наприклад, бінарне вираз, що представляє суму, матиме тип BinaryExpr <ExprT1, ExprT2, plus <double>>. Оскільки це ім'я типу досить громіздке, для більш зручного використання нашого рішення ми додамо створюють функції.

створюють функції

Створюють функції - широко використовувана техніка в поєднанні з шаблонним програмуванням. У STL існує багато прикладів створюють функцій, наприклад, make_pair () одна з них. Створюють функції є допоміжними функціями, які використовують той факт, що компілятор автоматично виводить типові аргументи шаблонів функцій, в той час як для шаблонів класів такого автоматичного висновку не існує.

Кожен раз, коли ми створюємо об'єкт типу, який генерується з шаблону класу, ми повинні повністю вказати сгенерированное ім'я типу, включаючи всі типові аргументи шаблону. Часто ці генеруються типові імена є дуже довгими і їх важко читати і розуміти. Як приклад розглянемо пару пар. Таким типом буде щось на зразок pair <pair <string, complex <double>>, pair <string, complex <double>>>. Створюють функції роблять життя користувачів шаблонів набагато простіше: створюють функції створюють об'єкт типу, який генерується з шаблону класу без необхідності вказівки довгих імен типу.

Більш точно, що створюють функціями є шаблони функцій, і вони мають ті ж самі типові шаблонні аргументи, що і клас шаблону, який описує тип об'єкта, який повинен бути створений. У нашому прикладі пар, шаблон класу pair має два аргументи типу T1 і T2, які представляють типи включаються елементів і створює функція make_pair () має ті ж два аргументи типу.

template <class T1, class T2> class pair {public: pair (T1 t1, T2 t2); }; template <class T1, class T2> pair <T1, T2> make_pair (t1 t1, T2 t2) {return pair <T1, T2> (t1, t2); }

Створюють функції схожі на конструктор: аргументи, які ми передаємо в створює функцію є точно такими ж аргументами, які б ми передали конструктору створюваного об'єкта в іншому випадку. Так як компілятор автоматично виводить аргументи шаблонів функцій, нам не потрібно вказувати всі шаблонні аргументи при виклику створює функції; компілятор буде автоматично виводити їх з аргументів, переданих створює функції. Замість створення пари через конструктор

pair <pair <string, complex <double >>, pair <string, complex <double>>> (pair <string, complex <double>> ( "origin", complex <double> (0,0)), pair < string, complex <double>> ( "saddle", aCalculation ()))

ми можемо створювати пару засобами створює функції

make_pair (make_pair ( "origin", complex <double> (0,0)), make_pair ( "saddle", aCalculation ()))

Ми будемо використовувати методику створюють функцій для полегшення створення наших об'єктів виразів. У лістингу 12 нижче показано два приклади цих створюють функцій:

Лістинг 12: Створюють функції для об'єктів виразів

template <class ExprT1, class ExprT2> BinaryExpr <ExprT1, ExprT2, plus <double>> makeSum (ExprT1 e1, ExprT2 e2) {return BinaryExpr <ExprT1, ExprT2, plus <double>> (e1, e2); } Template <class ExprT1, class ExprT2> BinaryExpr <ExprT1, ExprT2, multiplies <double>> makeProd (ExprT1 e1, ExprT2 e2) {return BinaryExpr <ExprT1, ExprT2, multiplies <double>> (e1, e2); }

Лістинг 13 показує використання шаблонної реалізації інтерпретатора для розрахунку вираження (x + 2) * 3.

Лістинг 13: Використання шаблонного інтерпретатора для арифметичних виразів

void someFunction (double x) {BinaryExpr <BinaryExpr <Variable, Literal, plus <double>>, Literal, multiplies <double>> expr = makeProd (makeSum (Variable (x), Literal (2)), Literal (3)) ; cout << expr.eval () << endl; }

По-перше, створюється об'єкт вираження, який представляє вираз (х + 2) * 3, а потім розраховується сам вираз. Зауважимо, що в цьому рішенні тип об'єкта вираження вже відображає структуру синтаксичного дерева.

Часто досить довге ім'я типу об'єкта вираження навіть не потрібно вказувати. У наведеному вище прикладі нам не потрібна змінна expr ми можемо безпосередньо використовувати результати створює функції makeProd () для розрахунку вираження, як показано нижче:

cout << makeProd (makeSum (Variable (x), Literal (2)), Literal (3)). eval () << endl;

оцінка

Що ми отримали за допомогою реалізації інтерпретатора на основі шаблонів, а не успадкування? Якщо компілятор вбудовує все створюють функції, конструктори і функції Eval () (швидше за все так і буде, так як вони тривіальні) вираз

cout << makeProd (makeSum (Variable (x), Literal (2)), Literal (3)). eval () << endl;

зводиться до (x + 2) * 3.

Порівняйте це з

Product expr (new Sum (new Variable (x), new Literal (2)), new Literal (3)). Eval ()

(Див. Лістинг 10). Воно призводить до ряду розподілів пам'яті з купи і наступних конструювання, а також декільком викликам віртуальної функції eval (). Швидше за все, жоден з викликів eval () не вбудованим, так як компілятори зазвичай не вбудовують функції, які викликаються через покажчики.

Рішення на основі шаблонів виконується набагато швидше, ніж об'єктно-орієнтована реалізація.

Подальше вдосконалення рішення на основі шаблонів

Давайте налаштуємо шаблони виразів і перетворимо їх в щось дійсно корисне. Спочатку покращимо їх читаність. Ми хочемо зробити вираз, таке як

makeProd (makeSum (Variable (x), Literal (2)), Literal (3)). eval ()

зручнішим для читання в тому сенсі, щоб воно виглядало більш-менш схоже на вираз, яку вона представляє, а саме: (х + 2) * 3. Це може бути досягнуто шляхом перевантаження операторів. Шляхом незначних модифікацій висловом можна надати такий вигляд eval ((v + 2) * 3.0).

Перша зміна полягає в перейменуванні створюють функцій так, щоб вони були перевантаженими операторами; тобто ми перейменуємо makeSum () в operator + (), makeProd () в operator * (), і так далі. тоді

makeProd (makeSum (Variable (x), Literal (2)), Literal (3))

перетворюється в

((Variable (x) + Literal (2)) * Literal (3))

Це звичайно добре, але недостатньо добре. Ми хотіли б написати ((х + 2) * 3). Таким чином, наша мета полягає в усуненні створення змінних і літералів, які як і раніше захаращують вираз.

Для того щоб з'ясувати, як ми можемо покращити наше рішення, розглянемо, що означає вираз х + 2, яке ми перейменували з створює функції makeSum () в operator + (). Реалізація operator + () показана в лістингу 14 нижче.

Лістинг 14: створити функція для вираження суми через перевантажений operator +

template <class ExprT1, class ExprT2> BinaryExpr <ExprT1, ExprT2, plus <double>> operator + (ExprT1 e1, ExprT2 e2) {return BinaryExpr <ExprT1, ExprT2, plus <double>> (e1, e2); }

Ми б хотіли, щоб x + 2 відповідав operator + (x, 2), який раніше був makeSum (х, 2). З цієї причини x + 2 є результатом створення об'єкта бінарного вираження, що представляє собою суму і з яким в якості аргументів були передані змінна х типу double і буквальний 2 типу int. Більш точно, це безіменний об'єкт, створений як BinaryExpr <double, int, plus <double >> (x, 2). Зверніть увагу, що тип об'єкта, це не зовсім те, що ми хочемо. Нам потрібно створити об'єкт типу BinaryExpr <Variable, Literal, plus <double >>, але при автоматичному виведення шаблонних аргументів невідомо, що х є змінною, а 2 літералом. Компілятор виводить тип double з аргументу х і тип int з аргументу 2, тому що він перевіряє типи аргументів, переданих функції.

Виходить, що ми повинні трохи допомогти компілятору вивести те, що нам необхідно. Якби ми передали об'єкт типу Variable замість оригінальної змінної х, то автоматичний висновок аргументів дав би результат типу BinaryExpr <Variable, int, plus <double >>, який трохи ближче до мети. (Ми розглянемо залишився перетворення int в Literal через хвилину). З цієї причини, мінімальна ступінь співробітництва з боку користувачів неминуча: вони повинні обернути свої змінних в об'єкти типу Variable, щоб це спрацювало, як показано в лістингу 15:

Лістинг 15: Використання шаблонного інтерпретатора для арифметичних виразів

void someFunction (double x) {Variable v = x; cout << ((v + 2) * 3) .eval () << endl; }

При використанні об'єкта v типу Variable замість простої числової змінної, ми домоглися того, що такий вислів, як v + 2 розраховується як неіменованого об'єкт BinaryExpr <Variable, int, plus <double >> (v, 2). Такий об'єкт BinaryExpr має два члена даних типу Variable і int відповідно. Обчислювальна функція BinaryExpr <Variable, int, plus <double >> :: eval () буде повертати суму двох членів даних. Суть в тому, що член даних int не знає, як себе обчислити, ми повинні перетворити літерал 2 в об'єкт типу Literal, який вже знає, як себе знайти. Як ми можемо автоматично перетворити константи будь-якого числового типу в об'єктів типу Literal? Для того, щоб вирішити цю проблему ми визначимо властивості виразів traits.

властивості traits

Використання властивостей є ще однією поширеною методикою програмування в поєднанні з шаблонним програмуванням. Клас властивостей це супроводжуючий клас, який слід разом з іншим типом і містить інформацію, яка пов'язана з цим іншим типом.

Стандартна бібліотека C ++ має кілька прикладів властивостей: властивості символів є прикладом. Як ви знаєте, стандартний клас string є шаблоном, який параметризрвані символьним типом для подання вузьких і широких символів. В принципі, шаблон класу string, який насправді називається basic_string, може створюватися з будь-яким типом символів, а не тільки з двома символьними типами, які зумовлені в мови C ++. Якщо, скажімо, хтось повинен представляти японські символи структурою Jchar, то шаблон basic_string може використовуватися для створення строкового класу для японських символів, а саме basic_string <Jchar>.

Уявімо, що ви реалізуєте такий шаблон строкового класу. Ви виявите, що є потрібна вам інформація, але яка не міститься в символьному типі. Наприклад, як би ви вирахували довжину рядка? Підрахунком всіх символів в рядку до тих пір, поки не знайдете символ кінця рядка. Як дізнатися який символ є символом кінця рядка? Ми знаємо, що це '\ 0' в разі вузьких символів типу char, і існує відповідний символ кінця рядка для символів типу wchar_t, але як визначити символ кінця рядка для японських символів типу Jchar? Очевидно, що інформація про символ кінця рядка є частиною інформації, асоційованої з типом кожного символу, але не міститься в символьному типі. І це саме те, для чого використовуються властивості: вони надають інформацію, пов'язану з типом, але не міститься в типі.

Тип властивостей є супроводжуючим типом, як правило, це шаблон класу, який може бути створений або спеціалізований для групи типів і надає інформацію, пов'язану з кожним типом. Символьні властивості (див. Шаблон класу char_traits в стандартній бібліотеці C ++), наприклад, містить статичний символьну константу, що позначає символ кінця рядка для символьного типу.

Ми будемо застосовувати техніку traits для вирішення нашої проблеми шляхом перетворення числових літералів в об'єкти типу Literal. Ми визначимо властивості виразів, які для кожного типу виразу передбачають інформацію про те, як вони повинні зберігатися всередині об'єктів виразів, операндами яких є. Все суті числових типів повинні зберігатися у вигляді об'єктів типу Literal; всі об'єкти типу Variable повинні зберігатися як Variables; і все нетермінальні об'єкти виразів повинні також зберігатися. Лістинг 16 показує визначення властивостей виразів:

Лістинг 16: Властивості виразів

template <class ExprT> struct exprTraits {typedef ExprT expr_type; }; template <> struct exprTraits <double> {typedef Literal expr_type; }; template <> struct exprTraits <int> {typedef Literal expr_type; }; ...

Клас властивостей виразів визначає вкладений тип expr_type, який представляє собою тип виразу для об'єкта вираження. Існує шаблон загальних властивостей, який визначає тип виразу для всіх виразів, які відносяться до типів класів, таких як BinaryExpr, UnaryExpr або Variable. Крім того, існують спеціалізації шаблону класу для всіх вбудованих числових типів, таких як short, int, long, float, double і т. Д. Для всіх Некласові виразів тип виразу визначається як тип Literal.

Всередині визначення класів BinaryExpr і UnaryExpr ми будемо використовувати властивості виразів для визначення типів членів даних, що містять подвираженія.

Лістінг 17: Використання властівостей виразів

template <class ExprT1, class ExprT2, class BinOp> class BinaryExpr {public: BinaryExpr (ExprT1 e1, ExprT2 e2, BinOp op = BinOp ()): _expr1 (e1), _ expr2 (e2), _ op (op) {} double eval () const {return _op (_expr1.eval (), _ expr2.eval ()); } Private: exprTraits <ExprT1> :: expr_type _expr1; exprTraits <ExprT2> :: expr_type _expr2; BinOp _op; };

Завдяки використанню властивостей виразів об'єкт типу BinaryExpr <Variable, int, plus <double >> міститиме два операнда у вигляді об'єктів типів Variable і Literal, як і хотілося.

Тепер ми домоглися того, що вираз, таке як ((v + 2) * 3) .eval (), де v є Variable - обгорткою double змінної х, буде обчислюватися як (х + 2) * 3. Давайте зробимо деякі незначні зміни, щоб зробити його ще більш зручним для читання. Більшість людей знаходять дивним виклик функції-члена суті, яка виглядає як вираз. Якщо ми визначимо допоміжну функцію, ми можемо перетворити наше вираз ((v + 2) * 3) .eval () будь-що то, схоже на eval ((v + 2) * 3), яке виглядає більш природно для більшості людей, але залишається еквівалентом в іншому. Лістинг 18 показує цю допоміжну функцію:

Лістинг 18: Допоміжна функція eval ()

template <class ExprT> double eval (ExprT e) {return e.eval (); }

Малюнок 10 ілюструє, як вираз ((v + 2) * 3) .eval (), де v є Variable - обгорткою double змінної x, поступово розгортається під час компіляції до вираження (x + 2) * 3.

eval (), де v є Variable - обгорткою double змінної x, поступово розгортається під час компіляції до вираження (x + 2) * 3

Мал. 10. Обчислення об'єкта вираження (v + 2) * 3 на етапі компіляції

Повторне використання об'єктів виразів

Вам може все ще цікаво, в чому полягає користь від об'єктів виразів. Кожен об'єкт вираження являє синтаксичну декомпозицію арифметичного виразу; це синтаксичне дерево знає, як інтерпретувати себе для обчислення числового значення. В принципі, ми створили механізм для обчислення виразів, таке, яке вже вбудовано в мову. Отже, в чому ж сенс? Подальші мінімальні коригування нашого рішення приведуть нас до суті.

До сих пір інтерпретація синтаксичного дерева була статичною. Кожне синтаксичне дерево створюється і інтерпретується тільки один раз. Можливо і більш динамічне використання, де дане синтаксичне дерево може будуватися неодноразово при різних вхідних значеннях. Пам'ятайте, що в кінцевому підсумку ми хочемо обчислювати інтеграли, такі як

Пам'ятайте, що в кінцевому підсумку ми хочемо обчислювати інтеграли, такі як

використовуючи інтегруючу функцію, яка апроксимує інтеграл шляхом обчислення виразу для зазначеного ряду рівновіддалених точок в інтервалі:

template <class ExprT> double integrate (ExprT e, double from, double to, size_t n) {double sum = 0, step = (to-from) / n; for (double i = from + step / 2; i <to; i + = step) sum + = e.eval (i); return step * sum; }

Ця функція могла б використовуватися для апроксимації інтеграла x / (1 + x) з прикладу вище:

Identity <double> x; cout << integrate (x / (1.0 + x), 1.0,5.0,10) << endl;

Нам потрібен об'єкт вираження, який може бути неодноразово інтерпретований, що поки не дозволяє наш шаблон вираження. Для перетворення нашої статичної інтерпретації синтаксичного дерева в повторну необхідні незначні зміни. Ми просто повинні змінити все обчислювальні функції наших шаблонів так, щоб вони брали вхідний аргумент, а саме значення, за яким вони будуть робити обчислення. Нетермінальні вираження передаватимуть аргумент своїм подвираженія. Літерал буде приймати аргумент і ігнорувати його; він буде продовжувати повертати константне значення, яке він представляє. Мінлива Variable більше не буде повертати значення змінної, на яку вказує, а буде повертати значення аргументу. З цієї причини ми перейменуємо її в Identity. Лістинг 19 нижче, показує зміни в класах:

Лістинг 19: Шаблон вираження з повторним використанням

class Literal {public: Literal (double v): _val (v) {} double eval (double) const {return _val; } Private: const double _val; }; template <class T> class Identity {public: T eval (T d) const {return d; }}; template <class ExprT1, class ExprT2, class BinOp> class BinExpr {public: double eval (double d) const {return _op (_expr1.eval (d), _ expr2.eval (d)); }}; ...

Якщо ми додамо нетермінальні вираження для включення числових функцій, таких як sqrt (), sqr (), exp (), log () і т.д., ми можемо навіть обчислювати розподіл Гаусса:

Лістинг 20: Обчислення розподілу Гаусса

double sigma = 2.0, mean = 5.0; const double Pi = 3.141593; cout << integrate (1.0 / (sqrt (2 * Pi) * sigma) * exp (sqr (x-mean) / (- 2 * sigma * sigma)), 2.0,10.0,100) << endl;

Для виразів, які представляють числові функції, ми можемо використовувати функції зі стандартної бібліотеки C. Все, що нам потрібно зробити, це додати створюють функції для унарних або бінарних виразів, операції яких є однією з стандартних функцій C. У лістинг 21 показано кілька прикладів:

Лістинг 21: Числові функції як шаблони нетермінальних виразів

template <class ExprT> UnaryExpr <ExprT, double (*) (double)> sqrt (const ExprT & e) {return UnaryExpr <ExprT, double (*) (double)> (e, :: std :: sqrt);} template <class ExprT> UnaryExpr <ExprT, double (*) (double)> exp (const ExprT & e) {return UnaryExpr <ExprT, double (*) (double)> (e, :: std :: exp); } ...

З цими доповненнями у нас з'явилися потужні і високопродуктивні рішення для розрахунку арифметичних виразів. Можна припустити, що застосування методів, продемонстрованих в цій статті, дозволяє налаштувати шаблони для логічних виразів. Шляхом перейменування обчислювальної функції eval () в operator () (), яка є оператором виклику функції, ми можемо легко перетворити об'єкти виразів в функціональні об'єкти, які потім можуть бути використані в поєднанні з алгоритмами STL. Нижче наведено приклад логічного виразу, що використовуються в якості предиката для підрахунку елементів в списку:

list <int> l; Identity <int> x; count_if (l.begin (), l.end (), x> = 0 && x <= 100);

Цей приклад добре ілюструє читабельність Expression шаблонів при нульовій обчислювальної вартості на етапі виконання. Шаблони виразів можуть бути зручні у використанні. Створення бібліотеки шаблонів зовсім інша історія, вона включає в себе розуміння більшого числа інших методик шаблонного програмування, крім тих, які ми розглянули в цій статті. Проте, всі бібліотеки шаблонів будуються на принципах, описаних в цій статті.

Шаблони виразів (expression templates). Частина 1

джерело: http://www.angelikalanger.com/Articles/Cuj/ExpressionTemplates/ExpressionTemplates.htm

Як ми можемо автоматично перетворити константи будь-якого числового типу в об'єктів типу Literal?
Наприклад, як би ви вирахували довжину рядка?
Як дізнатися який символ є символом кінця рядка?
Ми знаємо, що це '\ 0' в разі вузьких символів типу char, і існує відповідний символ кінця рядка для символів типу wchar_t, але як визначити символ кінця рядка для японських символів типу Jchar?
Отже, в чому ж сенс?
Категории
  • Биология
  • Математика
  • Краеведению
  • Лечебная
  • Наука
  • Физике
  • Природоведение
  • Информатика
  • Новости

  • Новости
    https://banwar.org/
    Наша взаимовыгодная связь https://banwar.org/. Запустив новый сайт, "Пари Матч" обещает своим клиентам незабываемый опыт и возможность выиграть крупные суммы.


    Наши клиенты
    Клиенты

    Быстрая связь

    Тел.: (044) 587-84-78
    E-mail: [email protected]

    Имя:
    E-mail:
    Телефон:
    Вопрос\Комментарий: