Estou com dificuldades para entender a definição de templates. Meu entendimento básico é que as definições de templates permitem tornar o tipo de dados para retorno ou argumentos como genérico. Ou seja, um argumento de template é um tipo de dados para o qual o compilador instancia e vincula em tempo de compilação,
Mas não consigo entender definições complexas como as abaixo:
#include <iostream>
#include <type_traits>
template <unsigned n>
struct factorial : std::integral_constant<int,n * factorial<n-1>::value> {};
template <>
struct factorial<0> : std::integral_constant<int,1> {};
int main() {
std::cout << factorial<5>::value; // constexpr (no calculations on runtime)
return 0;
}
onde
template <class T, T v>
struct integral_constant {
static constexpr T value = v;
typedef T value_type;
typedef integral_constant<T,v> type;
constexpr operator T() { return v; }
};
Saída:
120
É realmente difícil visualizar como os modelos são instanciados:
template <int,5 * factorial<5-1>::value>
struct integral_constant {
static constexpr int value = 5 * factorial<5-1>::value;
typedef int value_type;
typedef integral_constant<5 * factorial<5-1>::value> type;
constexpr operator int() { return 5 * factorial<5-1>::value; }
};
O único uso de templates que você não mencionou, mas provavelmente está familiarizado, é armazenar dados genéricos, como
std::vector<int>
. Aqui, estamos usandointegral_constant
which meio que armazena dados como um membro estático constante.Então definimos uma
factorial
struct recursivamente, onde um caso base de 0 é "igual" a 1, enquanto qualquer outro é o número N multiplicado porfactorial<N - 1>
. Isso significa quefactorial<5>
se expande para:Então, seguindo a herança e a multiplicação, significa que
factorial<5> : std::integral_constant<int, 5 * 4 * 3 * 2 * 1 * 1>
efactorial<5>::value = 5 * 4 * 3 * 2 * 1 * 1 = 120
.Apesar da sintaxe estranha, isso não é diferente da definição "normal" de fatorial como uma função recursiva:
Embora em ambos os casos, eu aconselharia usar um tipo sem sinal ou adicionar uma verificação negativa para evitar que as chamadas recursivas explodam para
n < 0
.Quanto ao motivo pelo qual você pode querer calcular valores dessa forma, é porque é garantido que isso aconteça em tempo de compilação, permitindo coisas como
std::array<int, factorial<5>::value>
. É menos o caso no C++ moderno, ondeconstexpr
funções podem fazer mais, então marcarint factorial(int n)
como constexpr deve permitirstd::array<int, factorial(5)>
compilar no C++17 e mais recentes.