До сих пор большинство примеров оперировали значениями (вычисление цифр числа пи, факториала 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 определяется как...»
Во-вторых, он используется для облегчения универсального программирования. (Итераторы не были бы очень общей концепцией, если бы они не работали для самого простого из всех примеров, указателя на массив. Поэтому мы используем немного метапрограммирования, чтобы заполнить детали, необходимые для того, чтобы указатель считался допустимым. итератор).
Это довольно распространенный вариант использования метапрограммирования. Конечно, вы можете использовать его для широкого круга других целей (шаблоны выражений — еще один часто используемый пример, предназначенный для оптимизации дорогостоящих вычислений, а Boost.Spirit — пример того, что вы полностью выходите за рамки и позволяете вам определить свой собственный синтаксический анализатор при компиляции). время), но, вероятно, наиболее распространенное использование состоит в том, чтобы сгладить эти небольшие неровности и угловые случаи, которые в противном случае потребовали бы специальной обработки и сделали бы универсальное программирование невозможным.
person
jalf
schedule
11.06.2009