Большинство примеров до сих пор работали со значениями (вычисление цифр числа пи, факториал N или аналогичных), и это в значительной степени примеры из учебников, но в целом они не очень полезны. Просто сложно представить ситуацию, когда вам действительно нужен компилятор для вычисления 17-й цифры числа пи. Либо вы сами его жестко закодировали, либо вычислили во время выполнения.
Пример, который может быть более актуальным для реального мира, может быть следующим:
Допустим, у нас есть класс массива, где размер является параметром шаблона (так что это объявляет массив из 10 целых чисел: array<int, 10>
)
Теперь нам может потребоваться объединить два массива, и мы можем использовать немного метапрограммирования для вычисления результирующего размера массива.
template <typename T, int lhs_size, int rhs_size>
array<T, lhs_size + rhs_size> concat(const array<T, lhs_size>& lhs, const array<T, rhs_size>& rhs){
array<T, lhs_size + rhs_size> result;
// copy values from lhs and rhs to result
return result;
}
Очень простой пример, но, по крайней мере, типы имеют какое-то отношение к реальному миру. Эта функция генерирует массив правильного размера, это происходит во время компиляции и с полной безопасностью типов. И это вычисление чего-то, что мы не могли бы легко сделать либо путем жесткого кодирования значений (мы могли бы захотеть объединить множество массивов с различными размерами), либо во время выполнения (потому что тогда мы потеряли бы информацию о типе)
Чаще, однако, вы склонны использовать метапрограммирование для типов, а не для значений.
Хороший пример можно найти в стандартной библиотеке. Каждый тип контейнера определяет свой собственный тип итератора, но простые старые указатели могут также использоваться в качестве итераторов.
Технически итератор необходим для предоставления ряда членов typedef, таких как value_type
, а указатели, очевидно, этого не делают. Таким образом, мы используем немного метапрограммирования, чтобы сказать «о, но если тип итератора оказывается указателем, его value_type
должно использовать это определение вместо этого».
Об этом следует отметить две вещи. Во-первых, мы манипулируем типами, а не значениями. Мы не говорим, что «факториал N такой-то и такой-то», а, скорее, «value_type
типа T определяется как ...»
Второе - это то, что он используется для упрощения общего программирования. (Итераторы не были бы очень общей концепцией, если бы не работал для простейшего из всех примеров указатель на массив. Поэтому мы используем немного метапрограммирования, чтобы заполнить детали, необходимые для того, чтобы указатель считался действительным итератор).
Это довольно распространенный вариант использования метапрограммирования. Конечно, вы можете использовать его для широкого спектра других целей (шаблоны Expression - еще один часто используемый пример, предназначенный для оптимизации дорогостоящих вычислений, а Boost.Spirit - пример полного перебора и позволяющий вам определять собственный синтаксический анализатор при компиляции. время), но, вероятно, наиболее распространенное использование - это сгладить эти небольшие неровности и угловые случаи, которые в противном случае потребовали бы специальной обработки и сделали бы невозможным общее программирование.