Jump to content

Шаблоны выражений

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

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

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

[ редактировать ]

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

/// @brief class representing a mathematical 3D vector
class Vec : public std::array<double, 3> {
  public:
    using std::array<double, 3>::array; 
    // inherit constructor (C++11)
    // see https://en.cppreference.com/w/cpp/language/using_declaration
};


/// @brief sum 'u' and 'v' into a new instance of Vec
Vec operator+(Vec const &u, Vec const &v) {
    Vec sum;
    for (size_t i = 0; i < u.size(); i++) {
        sum[i] = u[i] + v[i];
    }
    return sum;
}

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

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

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

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

Пример реализации шаблонов выражений:

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

template <typename E>
class VecExpression {
  public:
    static constexpr bool is_leaf = false;

    double operator[](size_t i) const {
        // Delegation to the actual expression type. This avoids dynamic polymorphism (a.k.a. virtual functions in C++)
        return static_cast<E const&>(*this)[i];
    }
    size_t size() const { return static_cast<E const&>(*this).size(); }
};

Логическое значение is_leaf есть здесь, чтобы отметить VecExpressions, которые являются «листами», т. е. фактически содержат данные. Vec Класс — это лист, который хранит координаты полностью вычисленного векторного выражения и становится подклассом VecExpression.

class Vec : public VecExpression<Vec> {
    std::array<double, 3> elems;

  public:
    static constexpr bool is_leaf = true;

    decltype(auto) operator[](size_t i) const { return elems[i]; }
    decltype(auto) &operator[](size_t i)      { return elems[i]; }
    size_t size()               const { return elems.size(); }

    // construct Vec using initializer list 
    Vec(std::initializer_list<double> init) {
        std::copy(init.begin(), init.end(), elems.begin());
    }

    // A Vec can be constructed from any VecExpression, forcing its evaluation.
    template <typename E>
    Vec(VecExpression<E> const& expr) {
        for (size_t i = 0; i != expr.size(); ++i) {
            elems[i] = expr[i];
        }
    }
};

Сумма двух Vecs представлен новым типом, VecSum, шаблон которого основан на типах левой и правой частей суммы, так что его можно применять к произвольным парам Vec выражения. Перегруженный operator+ служит синтаксическим сахаром для VecSum конструктор. В данном случае вмешивается тонкость: чтобы при суммировании двух VecExpressionс, VecSum необходимо сохранить константную ссылку на каждый VecExpression если это лист, в противном случае это временный объект, который необходимо скопировать для правильного сохранения.

template <typename E1, typename E2>
class VecSum : public VecExpression<VecSum<E1, E2> > {
  // cref if leaf, copy otherwise
  typename std::conditional<E1::is_leaf, const E1&, const E1>::type _u;
  typename std::conditional<E2::is_leaf, const E2&, const E2>::type _v;

  public:
    static constexpr bool is_leaf = false;

    VecSum(E1 const& u, E2 const& v) : _u(u), _v(v) {
        assert(u.size() == v.size());
    }
    decltype(auto) operator[](size_t i) const { return _u[i] + _v[i]; }
    size_t size()               const { return _v.size(); }
};
  
template <typename E1, typename E2>
VecSum<E1, E2>
operator+(VecExpression<E1> const& u, VecExpression<E2> const& v) {
   return VecSum<E1, E2>(*static_cast<const E1*>(&u), *static_cast<const E2*>(&v));
}

С учетом приведенных выше определений выражение a + b + c имеет тип

VecSum<VecSum<Vec, Vec>, Vec>

так Vec x = a + b + c вызывает шаблон Vec конструктор Vec(VecExpression<E> const& expr) с его аргументом шаблона E будучи этим типом (что означает VecSum<VecSum<Vec, Vec>, Vec>). Внутри этого конструктора тело цикла

elems[i] = expr[i];

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

elems[i] = a.elems[i] + b.elems[i] + c.elems[i];

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

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

int main() {
    Vec v0 = {23.4,  12.5,  144.56};
    Vec v1 = {67.12, 34.8,  90.34};
    Vec v2 = {34.90, 111.9, 45.12};
    
    // Following assignment will call the ctor of Vec which accept type of 
    // `VecExpression<E> const&`. Then expand the loop body to 
    // a.elems[i] + b.elems[i] + c.elems[i]
    Vec sum_of_vec_type = v0 + v1 + v2; 

    for (size_t i = 0; i < sum_of_vec_type.size(); ++i)
        std::cout << sum_of_vec_type[i] << std::endl;

    // To avoid creating any extra storage, other than v0, v1, v2
    // one can do the following (Tested with C++11 on GCC 5.3.0)
    auto sum = v0 + v1 + v2;
    for (size_t i = 0; i < sum.size(); ++i)
        std::cout << sum[i] << std::endl;
    // Observe that in this case typeid(sum) will be VecSum<VecSum<Vec, Vec>, Vec>
    // and this chaining of operations can go on.
}

Приложения

[ редактировать ]

Шаблоны выражений оказались особенно полезными авторами библиотек для линейной алгебры, то есть для работы с векторами и матрицами чисел. Среди библиотек, использующих шаблон выражений, — Dlib , Armadillo , Blaze , [5] Блиц++ , [6] Увеличьте uBLAS, [7] Собственный , [8] БУМ, [9] Стэн Математическая библиотека , [10] и кстензор. [11] Шаблоны выражений также могут ускорить автоматического дифференцирования на C++. реализацию [12] как показано в библиотеке Adept .

Помимо векторной математики, платформа синтаксического анализатора Spirit использует шаблоны выражений для представления формальных грамматик и их компиляции в анализаторы.

См. также

[ редактировать ]
  1. ^ Перейти обратно: а б Мацузаки, Киминори; Эмото, Кенто (2009). Реализация параллельных скелетов, оснащенных слиянием, с помощью шаблонов выражений . Учеб. Международный симп. по реализации и применению функциональных языков. стр. 72–89.
  2. ^ Вандевурде, Дэвид; Джосуттис, Николай (2002). Шаблоны C++: Полное руководство . Эддисон Уэсли . ISBN  0-201-73484-2 .
  3. ^ Перейти обратно: а б Вельдхейзен, Тодд (1995). «Шаблоны выражений» . Отчет С++ . 7 (5): 26–31. Архивировано из оригинала 10 февраля 2005 года.
  4. ^ Абрахамс, Дэвид; Гуртовой, Алексей (2004). Метапрограммирование шаблонов C++: концепции, инструменты и методы из Boost и за его пределами . Пирсон Образование. ISBN  9780321623911 .
  5. ^ Битбакет
  6. ^ «Руководство пользователя Blitz++» (PDF) . Проверено 12 декабря 2015 г.
  7. ^ «Библиотека базовой линейной алгебры Boost» . Проверено 25 октября 2015 г.
  8. ^ Геннебо, Гаэль (2013). Eigen: библиотека линейной алгебры C++ (PDF) . Еврографика/CGLibs.
  9. ^ Вельдхейзен, Тодд (2000). Как раз тогда, когда вы думали, что ваш маленький язык безопасен: «Шаблоны выражений» на Java . Международный симп. Генеративная и компонентная разработка программного обеспечения. CiteSeerX   10.1.1.22.6984 .
  10. ^ «Стэн документация» . Проверено 27 апреля 2016 г.
  11. ^ «Многомерные массивы с трансляцией и ленивыми вычислениями» . Проверено 18 сентября 2017 г.
  12. ^ Хоган, Робин Дж. (2014). «Быстрое автоматическое дифференцирование в обратном режиме с использованием шаблонов выражений на C++» (PDF) . АКМ Транс. Математика. Программное обеспечение . 40 (4): 26:1–26:16. дои : 10.1145/2560359 . S2CID   9047237 .
Arc.Ask3.Ru: конец переведенного документа.
Arc.Ask3.Ru
Номер скриншота №: 74e5ccfc169add8f5b758949488a0ad7__1717696500
URL1:https://arc.ask3.ru/arc/aa/74/d7/74e5ccfc169add8f5b758949488a0ad7.html
Заголовок, (Title) документа по адресу, URL1:
Expression templates - Wikipedia
Данный printscreen веб страницы (снимок веб страницы, скриншот веб страницы), визуально-программная копия документа расположенного по адресу URL1 и сохраненная в файл, имеет: квалифицированную, усовершенствованную (подтверждены: метки времени, валидность сертификата), открепленную ЭЦП (приложена к данному файлу), что может быть использовано для подтверждения содержания и факта существования документа в этот момент времени. Права на данный скриншот принадлежат администрации Ask3.ru, использование в качестве доказательства только с письменного разрешения правообладателя скриншота. Администрация Ask3.ru не несет ответственности за информацию размещенную на данном скриншоте. Права на прочие зарегистрированные элементы любого права, изображенные на снимках принадлежат их владельцам. Качество перевода предоставляется как есть. Любые претензии, иски не могут быть предъявлены. Если вы не согласны с любым пунктом перечисленным выше, вы не можете использовать данный сайт и информация размещенную на нем (сайте/странице), немедленно покиньте данный сайт. В случае нарушения любого пункта перечисленного выше, штраф 55! (Пятьдесят пять факториал, Денежную единицу (имеющую самостоятельную стоимость) можете выбрать самостоятельно, выплаичвается товарами в течение 7 дней с момента нарушения.)