Метапрограммирование на C++: факториал

Метаалгоритм вычисления факториала

Это классический пример метапрограммирования на C++. Он демонстрирует три вещи в метаалгоритмах C++: переменные, циклы и альтернативы (ветвления).

#include <iostream>

template<int N>
struct Factorial
{
 enum { value = N * Factorial<N-1>::value };
};

template<>
struct Factorial<1>
{
 enum { value = 1 };
};

int main()
{
 // Example use
 const int fact5 = Factorial<5>::value;
 std::cout << fact5;
 return 0;
}
 

Константа fact5 в ходе компиляции получает значение 120 - факториал числа 5 (произведение 1*2*3*4*5). Все вычисления происходят именно на стадии компиляции. Цикл организуется рекурсивной подстановкой шаблона Factorial<T>, условие окончания цикла - специализация шаблона Factorial<1>.  В качестве переменной цикла используется оператор enum. Приглядитесь к объявлению поля enum { value } в теле цикла - рекурсия и перемножения сидят именно там.

Как работает метацикл

Любой цикл в метапрограмме организуется так, как в этом примере - через рекурсию. Поэтому давайте немного подробнее остановимся на том, какие же действия выполняет компилятор C++ при вычислении факториала.

Итак, исполнение метаалгоритма начинается со строчки

 const int fact5 = Factorial<5>::value;
 

в функции main. К этому времени компилятор знает определение шаблона Factorial<N> и его специализацию (частный случай) Factorial<1>. Для вычисления Factorial<5> подходит только общее определение шаблона, поэтому компилятор подставляет целочисленный аргумент 5 в параметр N шаблона и начинает развертывание описание шаблона, заменяя N везде на 5.

Дойдя до объявления перечисления enum в теле шаблона, компилятор обнаруживает, что первый элемент перечисления определен как произведение параметра N шаблона на константу, определенную как поле шаблона с параметром N-1 (то есть 4). Теперь компилятор должен создать специализацию шаблона Factorial для параметра 4.

Так возникает рекурсия специализаций шаблона Factorial для значений аргумента N равных 5, 4, 3, 2.

Как же прерывается цикл (рекурсия)? Благодаря явно объявленной специализации Factorial<1>. Правила развертывания шаблонов в C++ таковы, что компилятор сам делает специализацию только в том случае, если в программе ему не встретилась явно объявленная специализация. В определении Factorial<1> поле перечисления enum определено как 1, то есть далее продолжать рекурсию не надо.

В итоге, выполнив все подстановки, компилятор получает во время компиляции значение константы fact5 равное 120.

 

Ограничения на рекурсию

При написании метапрограмм следует помнить, что максимально допустимая глубина рекурсии - вещь неопределенная, для каждой версии компилятора она индивидуальная. Поэтому вполне может статься, что вышеприведенный пример не будет работать вообще, или он не сможет вычислить факториал чисел больше 17 - проверьте самостоятельно.

 

Скачать пример

Файл factorial.cpp (293 байта)

  © Mental Computing 2010