Обновление: на Reddit мне сказали, что это старая перспектива программирования на C ++ и оптимизации компилятора. Если вы знакомы с метапрограммированием шаблонов и хотите получать последние обновления, перейдите к комментариям к сообщению Reddit.
Что такое метапрограммирование шаблонов?
« Под метапрограммированием понимается множество способов, которыми программа познает себя или может манипулировать собой». читает Википедию. Когда я впервые прочитал его, будучи начинающим аспирантом, я был поражен возможностью программ ИИ, способных писать другие фрагменты кода и захватывать мир. Это голливудская версия, но вот реалистичная, которая используется в отрасли для оптимизации времени компиляции с использованием шаблонов C ++.
Я столкнулся с этой концепцией во время исследования побочного проекта, связанного с оптимизацией. Хотя это понятие не далеко от основ курса CS101, оно не совсем хорошо известно, и, следовательно, блог не может его разбить.
Давайте для начала посмотрим, что такое шаблоны:
Шаблоны - это основа общего программирования, которое предполагает написание кода таким образом, чтобы он не зависел от какого-либо конкретного типа переменной.
Возьмем, к примеру, следующий код:
#include <iostream>
using
namespace
std;
// One function works for all data types.
template
<typename
T> T tMax(T x, T y)
{ return
(x > y)? x: y; }
int
main() {
cout << tMax<int>(3, 7) << endl; // Call myMax for
type intcout << tMax<double>(3.0, 7.0) << endl; // call myMax for
type dcout << tMax<char>(’g’, 'e’) << endl; // call myMax for type char
return
0; }
Шаблон tMax
предоставляет универсальную функцию, которая может использоваться всеми типами данных.
Как работают шаблоны?
Шаблоны компилируются в реальные типы ассемблером
Сами шаблоны не существуют как универсальные после компиляции программы, то есть в коде сборки.
Как и в примере tMax
шаблона, который мы видели выше, шаблон tMax преобразуется ассемблером в разные функции для каждого из типов, вызываемых в коде.
ref: Assembly Code (строка 38 определяет функцию int tMax ‹int› (int, int), которая является tMax для типа int)
Вы можете перейти к Учебному руководству (шаблоны C ++), чтобы потратить некоторое время на шаблоны.
Конструкция, эквивалентная шаблонам, предоставляемым Java, - Generics.
Использование шаблонов для оптимизации
Поскольку шаблоны «разворачиваются» в функции во время компиляции, а компилятор также выполняет вычисления, в которых переменные не задействованы непосредственно во время компиляции, некоторые вычисления могут быть выполнены во время компиляции и их можно избежать во время выполнения как результат.
Вычисления можно завершить во время компиляции и избежать во время выполнения
Допустим, вы пишете фрагмент кода и хотите использовать значение 15! (15 факториал) как часть кода.
Рекурсивный способ сделать это - использовать следующий код:
long factorial(int n) { if (n == 0) return 1; else return(n * factorial(n-1)); } int main() { cout << factorial(15) << endl; return 0; }
Это компилируется в функцию, и вызов этой функции с аргументом n = 15 будет выполнен во время выполнения.
ref: Assembly Code (вы можете увидеть функцию factorial и call factorial (int) в строке 17)
Давайте теперь конвертируем в программу с помощью шаблонов.
template <long N> struct Factorial { enum { value = N * Factorial<N - 1>::value }; }; template <> struct Factorial<0> { enum { value = 1 }; }; int main() { cout << Factorial<15>::value << endl; return 0; }
Во время компиляции компилятор разворачивает шаблон Factorial для аргумента N = 15 и встречает шаблон для аргумента N = 14 и так далее до N = 0. Поскольку во время этих вычислений переменные не встречаются, они разрешаются во время компиляции, факториал ‹15› в конечном итоге принимает значение 15! то есть 1307674368000 во время компиляции, избегая любых вычислений во время выполнения. Вы можете видеть в собранном коде, что значение 1307674368000 используется непосредственно, чем вызов функции.
ref: Assembly Code (Нет определения функции, так как значение 15! уже вычислено ассемблером и используется в строке 4)
Выполнение обеих реализаций с временным профилированием (и слишком большим количеством итераций) показывает нам влияние оптимизации. Шаблонный код выполняется за ~ 0,03 мс, в то время как рекурсивная версия работает как минимум в 30 раз дольше, как ожидалось, вычисляя факториал и снижая рекурсию во время выполнения.
Другие примеры: шаблоны и специализированные функции.
Более глубокий (более сложный, более полезный) пример взят из Метапрограммы шаблонов (Тодд Велдхуизен). Использование шаблонов для пузырьковой сортировки не кажется интуитивно понятным, поскольку вводится не одно число, а идея состоит в том, чтобы создать шаблон, который разворачивается в specialised function
для пузырьковой сортировки, обслуживающей определенное количество элементы. Я настоятельно рекомендую просмотреть пример и проработать детали.
Где это используется?
Первая мысль, которая пришла мне в голову, заключалась в том, почему нам нужно выполнять такую функцию, как вызов шаблона, если не задействованы переменные. Разве это не эквивалентно написанию кода с самим значением (скажем, 1307674368000 вместо Factorial ‹15›). Противоположным аргументом этому является требование абстрагироваться от функций вместо того, чтобы разбрасывать случайные значения по коду.
Следовательно, библиотеки в Boost используют шаблоны для предоставления библиотек для сложных функций, таких как факториалы, синус, косинус и т. Д., Чтобы гарантировать, что значения не вычисляются во время выполнения, где бы они ни были выполнены во время компиляции.
В общем, шаблоны представляют собой хитрый трюк, обеспечивающий вычисление времени компиляции и экономию времени выполнения в критичных по времени проектах (высокочастотная торговля, возможно, воздушные и космические путешествия).
Посетите SO для обсуждения сообществом метапрограммирования шаблонов. Прокомментируйте ниже, если вы использовали это в своем коде и как.