Template metaprogramming
![]() | Эта статья включает список общих ссылок , но в ней отсутствуют достаточные соответствующие встроенные цитаты . ( июнь 2010 г. ) |
Метапрограммирование шаблонов ( TMP ) — это метод метапрограммирования , при котором шаблоны используются компилятором для создания временного исходного кода , который компилятор объединяет с остальным исходным кодом и затем компилирует. Вывод этих шаблонов может включать времени компиляции константы , структуры данных и полные функции . Использование шаблонов можно рассматривать как полиморфизм времени компиляции . Этот метод используется во многих языках, наиболее известным из которых является C++ , а также Curl , D , Nim и XL .
Метапрограммирование шаблонов было в некотором смысле открыто случайно. [1] [2]
Некоторые другие языки поддерживают аналогичные, если не более мощные, средства времени компиляции (например, Lisp макросы ), но они выходят за рамки этой статьи.
Components of template metaprogramming [ edit ]
Использование шаблонов в качестве метода метапрограммирования требует двух различных операций: необходимо определить шаблон и создать экземпляр определенного шаблона . Общая форма сгенерированного исходного кода описана в определении шаблона, и при создании экземпляра шаблона общая форма в шаблоне используется для создания определенного набора исходного кода.
Метапрограммирование шаблонов является полным по Тьюрингу , что означает, что любое вычисление, выражаемое компьютерной программой, может быть вычислено в той или иной форме метапрограммой шаблона. [3]
Шаблоны отличаются от макросов . Макрос — это фрагмент кода, который выполняется во время компиляции и либо выполняет текстовые манипуляции с компилируемым кодом (например, C++ макросы ), либо манипулирует абстрактным синтаксическим деревом , создаваемым компилятором (например, макросы Rust или Lisp ). Текстовые макросы значительно более независимы от синтаксиса языка, которым манипулируют, поскольку они просто изменяют текст исходного кода в памяти непосредственно перед компиляцией.
Метапрограммы шаблонов не имеют изменяемых переменных — то есть ни одна переменная не может изменить значение после своей инициализации, поэтому метапрограммирование шаблонов можно рассматривать как форму функционального программирования . Фактически, многие реализации шаблонов реализуют управление потоком только посредством рекурсии , как показано в примере ниже.
Using template metaprogramming [ edit ]
Хотя синтаксис метапрограммирования шаблонов обычно сильно отличается от языка программирования, на котором оно используется, он имеет практическое применение. Некоторые распространенные причины использования шаблонов — реализация общего программирования (избегание разделов кода, которые похожи, за исключением некоторых незначительных изменений) или выполнение автоматической оптимизации времени компиляции, например выполнение чего-либо один раз во время компиляции, а не при каждом запуске программы. например, заставляя компилятор разворачивать циклы для устранения переходов и уменьшения количества циклов при каждом выполнении программы.
Генерация классов во время компиляции [ править ]
Что именно означает «программирование во время компиляции», можно проиллюстрировать на примере функции факториала, которую на нешаблоном C++ можно записать с использованием рекурсии следующим образом:
беззнаковый факториал ( беззнаковый n ) {
return n == 0 ? 1 : n * факториал ( n - 1 );
}
// Примеры использования:
// факториал(0) даст 1;
// факториал(4) даст 24.
Приведенный выше код будет выполняться во время выполнения для определения факториала литералов 0 и 4. Используя метапрограммирование шаблонов и специализацию шаблонов для обеспечения конечного условия рекурсии, факториалы, используемые в программе (игнорируя любые неиспользуемые факториалы), могут быть вычислены во время компиляции с помощью этого кода:
шаблон < беззнаковое N >
struct Factorial {
static constexpr беззнаковое значение = N * факториал < N - 1 >:: value ;
};
шаблон <>
struct Factorial < 0 > {
static constexpr беззнаковое значение = 1 ;
};
// Примеры использования:
// Factorial<0>::value даст 1;
// Factorial<4>::value даст 24.
Приведенный выше код вычисляет факториал литералов 0 и 4 во время компиляции и использует результаты, как если бы они были предварительно рассчитанными константами. Чтобы иметь возможность использовать шаблоны таким образом, компилятор должен знать значение своих параметров во время компиляции, что имеет естественное предварительное условие: факториал<X>::value может использоваться только в том случае, если X известен во время компиляции. Другими словами, X должен быть константным литералом или константным выражением.
В C++11 и C++20 и consteval , были введены constexpr позволяющие компилятору выполнять код. Используя constexpr и consteval, можно использовать обычное рекурсивное определение факториала с нешаблонным синтаксисом. [4]
Оптимизация кода во время компиляции [ править ]
Приведенный выше пример факториала является одним из примеров оптимизации кода во время компиляции, при котором все факториалы, используемые программой, предварительно компилируются и вводятся как числовые константы при компиляции, что позволяет сэкономить как накладные расходы во время выполнения, так и объем памяти . Однако это относительно незначительная оптимизация.
В качестве еще одного, более значимого примера развертывания цикла во время компиляции можно использовать метапрограммирование шаблонов для создания длиной n векторных классов (где n известно во время компиляции). Преимущество по сравнению с более традиционным вектором длины n заключается в том, что циклы можно разворачивать, что приводит к очень оптимизированному коду. В качестве примера рассмотрим оператор сложения. Сложение векторов длины n можно записать как
шаблон < int длина >
Vector < длина >& Vector < длина >:: оператор += ( const Vector < длина >& rhs )
{
for ( int i = 0 ; i < length ; ++ i )
value [ i ] += РЗС . значение [ я ];
вернуть * это ;
}
Когда компилятор создает экземпляр шаблона функции, определенного выше, может быть создан следующий код: [ нужна цитата ]
шаблон <>
Vector < 2 >& Vector < 2 >:: оператор += ( const Vector < 2 >& rhs )
{
value [ 0 ] += rhs . значение [ 0 ];
значение [ 1 ] += правая шкала . значение [ 1 ];
вернуть * это ;
}
Оптимизатор компилятора должен иметь возможность развернуть for
цикл, потому что параметр шаблона length
является константой во время компиляции.
Однако будьте осторожны и соблюдайте осторожность, так как это может привести к раздуванию кода, поскольку для каждого N (размера вектора), с которым вы создаете экземпляр, будет генерироваться отдельный развернутый код.
Статический полиморфизм [ править ]
Полиморфизм — это общепринятое стандартное средство программирования, в котором производные объекты могут использоваться как экземпляры их базового объекта, но при этом будут вызываться методы производных объектов, как в этом коде.
class Base
{
public :
virtual void метод () { std :: cout << "Base" ; }
виртуальный ~ Base () {}
};
класс Derived : public Base
{
public :
virtual void метод () { std :: cout << "Derived" ; }
};
int main ()
{
Base * pBase = new Derived ;
pBase -> метод (); //выходы «Производные»
delete pBase ;
вернуть 0 ;
}
где все вызовы virtual
методы будут принадлежать наиболее производному классу. Это динамически полиморфное поведение (обычно) достигается путем создания виртуальных справочных таблиц для классов с виртуальными методами, таблиц, которые просматриваются во время выполнения для идентификации вызываемого метода. Таким образом, полиморфизм во время выполнения обязательно влечет за собой накладные расходы на выполнение (хотя в современных архитектурах накладные расходы невелики).
Однако во многих случаях необходимое полиморфное поведение является инвариантным и может быть определено во время компиляции. Затем шаблон любопытно повторяющегося шаблона можно использовать (CRTP) для достижения статического полиморфизма , который является имитацией полиморфизма в программном коде, но который разрешается во время компиляции и, таким образом, устраняет необходимость поиска в виртуальной таблице во время выполнения. Например:
шаблон < класс Derived >
struct base
{
void интерфейс ()
{
// ...
static_cast < Derived *> ( this ) -> реализация ();
// ...
}
};
структура производная : base <derived> { voidimplementation
(
; // )
{
...
}
}
Здесь шаблон базового класса будет использовать тот факт, что тела функций-членов не создаются до тех пор, пока они не будут объявлены, и он будет использовать члены производного класса в своих собственных функциях-членах посредством использования static_cast
, таким образом, при компиляции генерируется композиция объектов с полиморфными характеристиками. В качестве примера реального использования CRTP используется в Boost библиотеке итераторов . [5]
Другое подобное использование — это « трюк Бартона-Нэкмана », иногда называемый «ограниченным расширением шаблона», когда общие функциональные возможности могут быть помещены в базовый класс, который используется не как контракт, а как необходимый компонент для обеспечения соответствующего поведения при минимизации избыточность кода.
Генерация статической таблицы [ править ]
Преимущество статических таблиц — замена «дорогих» вычислений простой операцией индексации массива (примеры см. в справочной таблице ). В C++ существует несколько способов создания статической таблицы во время компиляции. В следующем листинге показан пример создания очень простой таблицы с использованием рекурсивных структур и шаблонов с переменным числом вариантов . Стол имеет размер десять. Каждое значение представляет собой квадрат индекса.
#include <iostream>
#include <array>
constexpr int TABLE_SIZE = 10 ;
/**
* Вариативный шаблон для рекурсивной вспомогательной структуры.
*/
template < int INDEX = 0 , int ... D >
struct Helper : Helper < INDEX + 1 , D ..., INDEX * INDEX > { };
/**
* Специализация шаблона для завершения рекурсии, когда размер таблицы достигает TABLE_SIZE.
*/
template < int ... D >
struct Helper < TABLE_SIZE , D ... > {
static constexpr std :: array < int , TABLE_SIZE > table = { D ... };
};
constexpr std :: массив < int , TABLE_SIZE > table = Helper <>:: table ;
enum {
FOUR = table [ 2 ] // использование времени компиляции
};
int main () {
for ( int i = 0 ; i < TABLE_SIZE ; i ++ ) {
std :: cout << table [ i ] << std :: endl ; // использование во время выполнения
}
std :: cout << "FOUR: " << FOUR << std :: endl ;
}
Идея заключается в том, что struct Helper рекурсивно наследуется от структуры с еще одним аргументом шаблона (в этом примере вычисляется как INDEX * INDEX), пока специализация шаблона не завершит рекурсию при размере 10 элементов. Специализация просто использует список переменных аргументов в качестве элементов массива. Компилятор создаст код, аналогичный следующему (взятый из clang, вызванного с помощью -Xclang -ast-print -fsyntax-only).
шаблон < int INDEX = 0 , int ... D > struct Helper : Helper < INDEX + 1 , D ..., INDEX * INDEX > {
};
шаблон <> struct Helper < 0 , <>> : Helper < 0 + 1 , 0 * 0 > {
};
шаблон <> struct Helper < 1 , < 0 >> : Helper < 1 + 1 , 0 , 1 * 1 > {
};
шаблон <> struct Helper < 2 , < 0 , 1 >> : Helper < 2 + 1 , 0 , 1 , 2 * 2 > {
};
шаблон <> struct Helper < 3 , < 0 , 1 , 4 >> : Helper < 3 + 1 , 0 , 1 , 4 , 3 * 3 > {
};
шаблон <> struct Helper < 4 , < 0 , 1 , 4 , 9 >> : Helper < 4 + 1 , 0 , 1 , 4 , 9 , 4 * 4 > {
};
шаблон <> struct Helper < 5 , < 0 , 1 , 4 , 9 , 16 >> : Helper < 5 + 1 , 0 , 1 , 4 , 9 , 16 , 5 * 5 > {
};
шаблон <> struct Helper < 6 , < 0 , 1 , 4 , 9 , 16 , 25 >> : Helper < 6 + 1 , 0 , 1 , 4 , 9 , 16 , 25 , 6 * 6 > {
};
template <> struct Helper < 7 , < 0 , 1 , 4 , 9 , 16 , 25 , 36 >> : Helper < 7 + 1 , 0 , 1 , 4 , 9 , 16 , 25 , 36 , 7 * 7 > {
};
шаблон <> struct Helper < 8 , < 0 , 1 , 4 , 9 , 16 , 25 , 36 , 49 >> : Helper < 8 + 1 , 0 , 1 , 4 , 9 , 16 , 25 , 36 , 49 , 8 * 8 > {
};
шаблон <> struct Helper < 9 , < 0 , 1 , 4 , 9 , 16 , 25 , 36 , 49 , 64 >> : Helper < 9 + 1 , 0 , 1 , 4 , 9 , 16 , 25 , 36 , 49 , 64 , 9 * 9 > {
};
template <> struct Helper < 10 , < 0 , 1 , 4 , 9 , 16 , 25 , 36 , 49 , 64 , 81 >> {
static constexpr std :: array < int , TABLE_SIZE > table = { 0 , 1 , 4 , 9 , 16 , 25 , 36 , 49 , 64 , 81 };
};
Начиная с C++17, это можно более читабельно записать так:
#include <iostream>
#include <array>
constexpr int TABLE_SIZE = 10 ;
constexpr std :: array < int , TABLE_SIZE > table = [] { // ИЛИ: constexpr auto table
std :: array < int , TABLE_SIZE > A = {};
for ( беззнаковый я = 0 ; я < TABLE_SIZE ; я ++ ) {
A [ я ] = я * я ;
}
Вернуть А ;
}();
enum {
FOUR = table [ 2 ] // использование времени компиляции
};
int main () {
for ( int i = 0 ; i < TABLE_SIZE ; i ++ ) {
std :: cout << table [ i ] << std :: endl ; // использование во время выполнения
}
std :: cout << "FOUR: " << FOUR << std :: endl ;
}
Чтобы показать более сложный пример, код в следующем листинге был расширен и теперь имеет помощник для вычисления значений (при подготовке к более сложным вычислениям), смещение для конкретной таблицы и аргумент шаблона для типа значений таблицы (например, uint8_t, uint16_t, ...).
#include <iostream>
#include <array>
constexpr int TABLE_SIZE = 20 ;
constexpr int OFFSET = 12 ;
/**
* Шаблон для расчета одной записи таблицы
*/
template < имя типа VALUETYPE , VALUETYPE OFFSET , VALUETYPE INDEX >
struct ValueHelper {
static constexpr VALUETYPE value = OFFSET + INDEX * INDEX ;
};
/**
* Вариативный шаблон для рекурсивной вспомогательной структуры.
*/
template < имя типа VALUETYPE , VALUETYPE OFFSET , int N = 0 , VALUETYPE ... D >
struct Helper : Helper < VALUETYPE , OFFSET , N + 1 , D ..., ValueHelper < VALUETYPE , OFFSET , N >:: value > { };
/**
* Специализация шаблона для завершения рекурсии, когда размер таблицы достигает TABLE_SIZE.
*/
template < имя типа VALUETYPE , VALUETYPE OFFSET , VALUETYPE ... D >
struct Helper < VALUETYPE , OFFSET , TABLE_SIZE , D ... > {
static constexpr std :: array < VALUETYPE , TABLE_SIZE > table = { D ... } ;
};
constexpr std :: array < uint16_t , TABLE_SIZE > table = Helper < uint16_t , OFFSET >:: table ;
int main () {
for ( int i = 0 ; i < TABLE_SIZE ; i ++ ) {
std :: cout << table [ i ] << std :: endl ;
}
}
Это можно записать следующим образом, используя C++17:
#include <iostream>
#include <array>
constexpr int TABLE_SIZE = 20 ;
constexpr int OFFSET = 12 ;
template < имя типа VALUETYPE , int OFFSET >
constexpr std :: array < VALUETYPE , TABLE_SIZE > table = [] { // ИЛИ: constexpr auto table
std :: array < VALUETYPE , TABLE_SIZE > A = {};
for ( беззнаковый я = 0 ; я < TABLE_SIZE ; я ++ ) {
A [ я ] = СМЕЩ + я * я ;
}
Вернуть А ;
}();
int main () {
for ( int i = 0 ; i < TABLE_SIZE ; i ++ ) {
std :: cout << table < uint16_t , OFFSET > [ i ] << std :: endl ;
}
}
Концепции [ править ]
Стандарт C++20 предоставил программистам C++ новый инструмент для программирования меташаблонов — концепции. [6]
Концепции позволяют программистам указывать требования к типу, чтобы сделать возможным создание экземпляра шаблона. Компилятор ищет шаблон с концепцией, предъявляющей самые высокие требования.
Вот пример знаменитой проблемы Fizz Buzz, решенной с помощью шаблонного метапрограммирования.
#include <boost/type_index.hpp> // для красивой печати типов
#include <iostream>
#include <tuple>
/**
* Типовое представление слов для печати
*/
struct Fizz {};
структура Buzz {};
структура FizzBuzz {};
шаблон < size_t _N > структуры номер { constexpr static size_t N = _N ; };
/**
* Концепции, используемые для определения условий для специализаций
*/
template < typename Any > Concept has_N = require { require Any :: N - Any :: N == 0 ; };
шаблон < имя типа A > концепция fizz_c = has_N < A > && требует { требует A :: N % 3 == 0 ; };
шаблон < имя типа A > концепция buzz_c = has_N < A > && требует { требует A :: N % 5 == 0 ;};
шаблон < имя типа A > концепция fizzbuzz_c = fizz_c < A > && buzz_c < A > ;
/**
* Путем специализации структуры `res` с учетом требований концепции выполняется правильное создание экземпляров
*/
template < typename X > struct res ;
шаблон < fizzbuzz_c X > struct res <X> { using result = FizzBuzz ; };
шаблон < fizz_c X > struct res <X> { using result = Fizz ; };
шаблон < buzz_c X > struct res <X> { using result = Buzz ; };
шаблон < has_N X > struct res <X> { using result = X ; };
/**
* Предварительное объявление конкатенатора
*/
template < size_t cnt , typename ... Args >
struct concatenator ;
/**
* Рекурсивный способ объединения следующих типов
*/
template < size_t cnt , typename ... Args >
struct concatenator < cnt , std :: tuple < Args ... >>
{ using type = typename concatenator < cnt - 1 , std :: tuple < typename res < number < cnt > >:: result , Args ... >>:: type ;};
/**
* Базовый случай
*/
template < typename ... Args > struct concatenator < 0 , std :: tuple < Args ... >> { using type = std :: tuple < Args ... > ;};
/**
* Метод получения окончательного результата
*/
template < size_t Amount >
using fizz_buzz_full_template = typename concatenator < Amount - 1 , std :: tuple < typename res < Number < Amount >>:: result >>:: type ;
int main ()
{
// результат печати с помощью boost, чтобы он был понятен
std :: cout << boost :: typeindex :: type_id < fizz_buzz_full_template < 100 >> (). красивое_имя () << std :: endl ;
/*
Результат:
std::tuple<number<1ul>, Number<2ul>, Fizz, Number<4ul>, Buzz, Fizz, Number<7ul>, Number<8ul>, Fizz, Buzz, Number<11ul>, Fizz , номер<13ul>, номер<14ul>, FizzBuzz, номер<16ul>, номер<17ul>, Fizz, номер<19ul>, Buzz, Fizz, номер<22ul>, номер<23ul>, Fizz, Buzz, номер< 26ul>, Fizz, номер<28ul>, номер<29ul>, FizzBuzz, номер<31ul>, номер<32ul>, Fizz, номер<34ul>, Buzz, Fizz, номер<37ul>, номер<38ul>, Fizz, Buzz, номер<41ul>, Fizz, номер<43ul>, номер<44ul>, FizzBuzz, номер<46ul>, номер<47ul>, Fizz, номер<49ul>, Buzz, Fizz, номер<52ul>, номер<53ul >, Fizz, Buzz, номер<56ul>, Fizz, номер<58ul>, номер<59ul>, FizzBuzz, номер<61ul>, номер<62ul>, Fizz, номер<64ul>, Buzz, Fizz, номер<67ul> , номер<68ul>, Fizz, Buzz, номер<71ul>, Fizz, номер<73ul>, номер<74ul>, FizzBuzz, номер<76ul>, номер<77ul>, Fizz, номер<79ul>, Buzz, Fizz, номер<82ul>, номер<83ul>, Fizz, Buzz, номер<86ul>, Fizz, номер<88ul>, номер<89ul>, FizzBuzz, номер<91ul>, номер<92ul>, Fizz, номер<94ul>, Buzz, Fizz, номер <97ul>, номер <98ul>, Fizz, Buzz>
*/
}
метапрограммирования шаблонов Преимущества и недостатки
Компромисс между временем компиляции и временем выполнения становится видимым, если используется большое количество шаблонного метапрограммирования.
- Метапрограммирование шаблонов позволяет программисту сосредоточиться на архитектуре и делегировать компилятору создание любой реализации, необходимой клиентскому коду. Таким образом, метапрограммирование шаблонов позволяет создавать действительно универсальный код , что способствует минимизации кода и повышению удобства сопровождения. [ нужна цитата ] .
- Что касается C++ до версии C++11 , синтаксис и идиомы метапрограммирования шаблонов были эзотерическими по сравнению с обычным программированием C++, и метапрограммы шаблонов могли быть очень трудными для понимания. [7] [8] Но начиная с C++11 синтаксис метапрограммирования вычислений значений становится все более и более похожим на «нормальный» C++, с все меньшим и меньшим ухудшением читаемости.
См. также [ править ]
- Неудачная замена не является ошибкой (SFINAE)
- Metaprogramming
- Препроцессор
- Параметрический полиморфизм
- Шаблоны выражений
- Вариатический шаблон
- Выполнение функции во время компиляции
Ссылки [ править ]
- ^ Скотт Мейерс (12 мая 2005 г.). Эффективный C++: 55 конкретных способов улучшить ваши программы и проекты . Пирсон Образование. ISBN 978-0-13-270206-5 .
- ^ См. Историю TMP в Wikibooks.
- ^ Вельдхуизен, Тодд Л. (2003). «Шаблоны C++ полны по Тьюрингу». CiteSeerX 10.1.1.14.3670 .
- ^ «Constexpr — Обобщенные константные выражения в C++11 — Cprogramming.com» . www.cprogramming.com .
- ^ «Фасад Итератора — 1.79.0» .
- ^ «Ограничения и концепции (начиная с C++20) — cppreference.com» . ru.cppreference.com .
- ^ Чарнецкий, К.; О'Доннелл, Дж.; Стригниц, Дж.; Таха, Валид Мохамед (2004). «Реализация DSL в метаокамле, шаблоне Haskell и C++» (PDF) . Университет Ватерлоо, Университет Глазго, Исследовательский центр Юлиха, Университет Райса.
Метапрограммирование шаблонов C++ страдает от ряда ограничений, в том числе проблем с переносимостью из-за ограничений компилятора (хотя за последние несколько лет ситуация значительно улучшилась), отсутствия поддержки отладки или ввода-вывода во время создания экземпляра шаблона, длительного времени компиляции, длинных и непонятных ошибок, плохой читаемость кода и плохая отчетность об ошибках.
- ^ Шеард, Тим; Джонс, Саймон Пейтон (2002). «Шаблон метапрограммирования для Haskell» (PDF) . АСМ 1-58113-415-0/01/0009.
В провокационной статье Робинсона шаблоны C++ названы главным, хотя и случайным, успехом разработки языка C++. Несмотря на чрезвычайно причудливую природу метапрограммирования шаблонов, шаблоны используются удивительными способами, выходящими за рамки самых смелых мечтаний разработчиков языка. Возможно, это удивительно, но, учитывая тот факт, что шаблоны являются функциональными программами, функциональные программисты не спешат извлекать выгоду из успеха C++.
- Эйзенекер, Ульрих В. (2000). Генеративное программирование: методы, инструменты и приложения . Аддисон-Уэсли. ISBN 0-201-30977-7 .
- Александреску, Андрей (2003). Современный дизайн на C++: применение общих шаблонов программирования и проектирования . Аддисон-Уэсли. ISBN 3-8266-1347-3 .
- Абрахамс, Дэвид ; Гуртовой, Алексей (январь 2005 г.). Метапрограммирование шаблонов C++: концепции, инструменты и методы из Boost и за его пределами . Аддисон-Уэсли. ISBN 0-321-22725-5 .
- Вандевурде, Дэвид ; Джосуттис, Николай М. (2003). Шаблоны C++: Полное руководство . Аддисон-Уэсли. ISBN 0-201-73484-2 .
- Клавель, Мануэль (16 октября 2000 г.). Отражение при переписывании логики: металогические основы и приложения метапрограммирования . ISBN 1-57586-238-7 .
Внешние ссылки [ править ]
- «Библиотека метапрограммирования Boost (Boost MPL)» .
- «Библиотека духов» . (построен с использованием шаблонного метапрограммирования)
- «Библиотека Boost Lambda» . (легко использовать алгоритмы STL)
- Вельдхейзен, Тодд (май 1995 г.). «Использование метапрограмм шаблонов C++» . Отчет С++ . 7 (4): 36–43. Архивировано из оригинала 4 марта 2009 г.
- «Шаблон Хаскелла» . (типобезопасное метапрограммирование в Haskell)
- Ярко, Уолтер . «Возвращение к шаблонам» . (шаблонное метапрограммирование на языке программирования D )
- Коскинен, Йоханнес . «Метапрограммирование в C++» (PDF) .
- Аттарди, Джузеппе; Чистернино, Антонио. «Поддержка отражения посредством метапрограммирования шаблонов» (PDF) .
- Бертон, Майкл С.; Грисволд, Уильям Г.; Маккалок, Эндрю Д.; Хубер, Гэри А. (2002). «Статические структуры данных». CiteSeerX 10.1.1.14.5881 .
- Амджад, Зишан (13 августа 2007 г.). «Шаблонное метапрограммирование и теория чисел» .
- Амджад, Зишан (24 августа 2007 г.). «Шаблонное метапрограммирование и теория чисел: Часть 2» .
- «Библиотека для программирования в стиле LISP на C++» .