Шаблоны выражений - Википедия - Expression templates

Шаблоны выражений площадь C ++ метапрограммирование шаблона техника, которая создает структуры, представляющие вычисление во время компиляции, где выражения оценивается только по мере необходимости для создания эффективного кода для всего вычисления.[1] Таким образом, шаблоны выражений позволяют программистам обойти обычный порядок оценки языка C ++ и добиться таких оптимизаций, как петля слияния.

Шаблоны выражений были изобретены независимо Тоддом Вельдхёйзеном и Дэвидом Вандевурдом;[2][3] их имя дал Велдхёйзен.[3] Это популярный метод выполнения линейная алгебра программного обеспечения.[1]

Мотивация и пример

Рассмотрим библиотеку, представляющую векторов и операции на них. Одна из распространенных математических операций - сложение двух векторов ты и vпоэлементно, чтобы создать новый вектор. Очевидной реализацией этой операции на C ++ была бы перегружен оператор + который возвращает новый векторный объект:

учебный класс Vec {    стандартное::вектор<двойной> Elems;  общественный:    Vec(size_t п) : Elems(п) {}    двойной &оператор[](size_t я)      { возвращаться Elems[я]; }    двойной оператор[](size_t я) const { возвращаться Elems[я]; }    size_t размер()               const { возвращаться Elems.размер(); }};Vec оператор+(Vec const &ты, Vec const &v) {    утверждать(ты.размер() == v.размер());    Vec сумма(ты.размер());    за (size_t я = 0; я < ты.размер(); я++) {         сумма[я] = ты[я] + v[я];    }    возвращаться сумма;}

Пользователи этого класса теперь могут писать Vec x = a + b; куда а и б оба экземпляра Vec.

Проблема с этим подходом состоит в том, что более сложные выражения, такие как Vec x = a + b + c реализованы неэффективно. Реализация сначала создает временный вектор для хранения а + б, затем создает другой вектор с элементами c добавлен. Даже с оптимизация возвращаемого значения это выделит память как минимум дважды и потребует двух циклов.

Отложенная оценка решает эту проблему и может быть реализована на C ++, если оператор + вернуть объект произвольного типа, скажем VecSum, который представляет собой неоцененную сумму двух векторов или вектор с VecSumи т. д. Более крупные выражения эффективно создают деревья выражения которые оцениваются только при присвоении фактическому Vec Переменная. Но это требует обхода таких деревьев для выполнения оценки, что само по себе дорого.[4]

Шаблоны выражений реализуют отложенную оценку с использованием деревьев выражений, которые существуют только во время компиляции. Каждое задание Vec, Такие как Vec x = a + b + c, генерирует новый Vec конструктор, если это необходимо для создания экземпляра шаблона. Этот конструктор оперирует тремя Vec; он выделяет необходимую память и затем выполняет вычисления. Таким образом, выполняется только одно выделение памяти.

Пример реализации шаблонов выражений выглядит следующим образом. Базовый класс VecExpression представляет любое векторное выражение. Он основан на фактическом типе выражения E должны быть реализованы в соответствии с любопытно повторяющийся шаблон шаблона. Существование базового класса, такого как VecExpression, не обязательно для работы шаблонов выражений. Он будет просто служить типом аргумента функции, чтобы отличать выражения от других типов (обратите внимание на определение конструктора Vec и operator + ниже).

 1 шаблон <typename E> 2 учебный класс VecExpression { 3   общественный: 4     двойной оператор[](size_t я) const  5     { 6         // Делегирование фактическому типу выражения. Это позволяет избежать динамического полиморфизма (он же виртуальные функции в C ++) 7         возвращаться static_cast<E const&>(*это)[я]; 8     } 9     size_t размер()               const { возвращаться static_cast<E const&>(*это).размер(); }10 };

В Vec класс по-прежнему хранит координаты полностью оцененного векторного выражения и становится подклассом VecExpression.

учебный класс Vec : общественный VecExpression<Vec> {    стандартное::вектор<двойной> Elems;  общественный:    двойной оператор[](size_t я) const { возвращаться Elems[я]; }    двойной &оператор[](size_t я)      { возвращаться Elems[я]; }    size_t размер() const               { возвращаться Elems.размер(); }    Vec(size_t п) : Elems(п) {}    // построить вектор, используя список инициализаторов     Vec(стандартное::initializer_list<двойной> в этом) : Elems(в этом) {}    // Vec может быть построен из любого VecExpression, принудительно вычисляя его.    шаблон <typename E>    Vec(VecExpression<E> const& expr) : Elems(expr.размер()) {        за (size_t я = 0; я != expr.размер(); ++я) {            Elems[я] = expr[я];        }    }};

Сумма двух векторов представлена ​​новым типом, VecSum, который основан на типах левой и правой частей суммы, поэтому его можно применять к произвольным парам векторных выражений. Перегруженный оператор + служит в качестве синтаксический сахар для VecSum конструктор.

 1 шаблон <typename E1, typename E2> 2 учебный класс VecSum : общественный VecExpression<VecSum<E1, E2> > { 3     E1 const& _u; 4     E2 const& _v; 5  6 общественный: 7     VecSum(E1 const& ты, E2 const& v) : _u(ты), _v(v) { 8         утверждать(ты.размер() == v.размер()); 9     }10 11     двойной оператор[](size_t я) const { возвращаться _u[я] + _v[я]; }12     size_t размер()               const { возвращаться _v.размер(); }13 };14   15 шаблон <typename E1, typename E2>16 VecSum<E1, E2>17 оператор+(VecExpression<E1> const& ты, VecExpression<E2> const& v) {18    возвращаться VecSum<E1, E2>(*static_cast<const E1*>(&ты), *static_cast<const E2*>(&v));19 }

С приведенными выше определениями выражение а + б + с относится к типу

VecSum<VecSum<Vec, Vec>, Vec>

так Vec x = a + b + c вызывает шаблонный Vec конструктор с этим типом в качестве E аргумент шаблона. Внутри этого конструктора тело цикла

Elems[я] = expr[я];

эффективно расширяется (следуя рекурсивным определениям оператор + и оператор [] на этом типе)

Elems[я] = а.Elems[я] + б.Elems[я] + c.Elems[я];

без временных векторов и только один проход через каждый блок памяти.

Основное использование:

 1 int главный() { 2     Vec v0 = {23.4,12.5,144.56,90.56}; 3     Vec v1 = {67.12,34.8,90.34,89.30}; 4     Vec v2 = {34.90,111.9,45.12,90.5}; 5      6     // Следующее присвоение вызовет ctor Vec, который принимает тип  7     // `VecExpression  const &`. Затем разверните тело цикла до  8     // a.elems [i] + b.elems [i] + c.elems [i] 9     Vec sum_of_vec_type = v0 + v1 + v2; 10 11     за (size_t я=0; я<sum_of_vec_type.размер(); ++я)12         стандартное::cout << sum_of_vec_type[я] << стандартное::конец;13 14     // Чтобы избежать создания дополнительного хранилища, кроме v0, v1, v215     // можно сделать следующее (проверено на C ++ 11 в GCC 5.3.0)16     авто сумма = v0 + v1 + v2;17     за (size_t я=0; я<сумма.размер(); ++я)18         стандартное::cout << сумма[я] << стандартное::конец;19     // Обратите внимание, что в этом случае typeid (sum) будет VecSum , Vec>20     // и эта цепочка операций может продолжаться.21 }

Приложения

Шаблоны выражений были сочтены особенно полезными авторами библиотек для линейной алгебры, т. Е. Для работы с векторами и матрицы номеров. Среди библиотек, использующих шаблон выражения, есть Длиб,[5] Армадилло, Пламя,[6] Блиц ++,[7] Способствовать росту uBLAS,[8] Эйген,[9] ПООМА,[10] Стандартная математическая библиотека,[11] и xtensor.[12] Шаблоны выражений также могут ускорить C ++ автоматическая дифференциация реализации,[13] как показано в Библиотека знатока.

За пределами векторной математики Фреймворк парсера духа использует шаблоны выражений для представления формальные грамматики и скомпилировать их в парсеры.

Рекомендации

  1. ^ а б Мацузаки, Киминори; Эмото, Кенто (2009). Реализация оснащенных Fusion параллельных скелетов с помощью шаблонов выражений. Proc. Int'l Symp. по реализации и применению функциональных языков. С. 72–89.
  2. ^ Вандевурде, Дэвид; Йосуттис, Николай (2002). Шаблоны C ++: полное руководство. Эддисон Уэсли. ISBN  0-201-73484-2.
  3. ^ а б Велдхёйзен, Тодд (1995). "Шаблоны выражений". Отчет C ++. 7 (5): 26–31. Архивировано из оригинал 10 февраля 2005 г.
  4. ^ Авраамс, Давид; Гуртовой, Алексей (2004). Метапрограммирование шаблонов C ++: концепции, инструменты и методы от Boost и не только. Pearson Education.
  5. ^ https://dlib.net
  6. ^ https://bitbucket.org/blaze-lib/blaze
  7. ^ «Руководство пользователя Blitz ++» (PDF). Получено 12 декабря, 2015.
  8. ^ "Расширьте базовую библиотеку линейной алгебры". Получено 25 октября, 2015.
  9. ^ Guennebaud, Гаэль (2013). Eigen: библиотека линейной алгебры C ++ (PDF). Eurographics / CGLibs.
  10. ^ Велдхёйзен, Тодд (2000). Когда вы думали, что ваш маленький язык безопасен: "Шаблоны выражений" в Java. Int'l Symp. Генеративная и компонентная разработка программного обеспечения. CiteSeerX  10.1.1.22.6984.
  11. ^ «Стандартная документация». Получено 27 апреля, 2016.
  12. ^ «Многомерные массивы с широковещательными и ленивыми вычислениями». Получено 18 сентября, 2017.
  13. ^ Хоган, Робин Дж. (2014). «Быстрое автоматическое дифференцирование в обратном режиме с использованием шаблонов выражений в C ++» (PDF). ACM Trans. Математика. Softw. 40 (4): 26:1–26:16.