Templates - Parte 2
Templates - Parte 2
Templates necessitam de parâmetros. Parâmetros podem ser:
- [[#Parâmetros type template]]*
- [[#Parâmetros non-type template]]*
- Parâmetros template de template**
- Será utilizado a terminologia em inglês
Apenas ressaltar uma diferença conceitual entre parâmetros e argumentos, que será usada algumas vezes neste tutorial. Parâmetro é a definição, a variável usada na declaração da função, enquanto argumento é o valor que será passado na função.
// T, parametro1, parametro2 são todos parâmetros !
template <typename T>
T soma(T parametro1, T parametro2)
{
return parametro1 + parametro2;
}
int main
{
int argumento1 {3};
int argumento2 {5};
// int, argumento1, argumento2 são argumentos!
int resultado = soma<int>(argumento1, argumento2);
}
Parâmetros type template
Nessa forma, os parâmetros assumem valores de tipo tais como int, float, double no processo de compilação. Você pode atribuir valores padrão para esses parâmetros conforme o exemplo abaixo.
// Partes do código foram omitidas propositadamente
template <typename T = double>
class Termometro
{
template <typename U = int>
U as() const
{
return static_cast<U>(temperatura);
}
Variadic Template (Parâmetro pack)
Na versão foi introduzido um conceito de template que aceita zero ou mais argumentos variáveis, também chamado de variadic template ou parâmetro pack[1].
Para indicar o uso de um variadic template usamos um símbolo denominado elipsis (representado por três pontinhos) após class... ou typename...
template <typename... T>
class wrapper { /* ... */ };
Exemplo adaptado livro [2].
template <typename T, typename... Args>
void funcao(const T &t, const Args& ... rest)
{
}
// Args é um parâmetro pack e representa zero ou mais parâmetros
// rest é um parâmetro pack de função e representa zero ou mais parâmetros de função
int main()
{
int i = 0;
double pi = 3.14;
string s = "Yellow submarine!";
funcao(i, s, 42, pi); // 3 parâmetros no pack
funcao(s, 42, "hi"); // 2 parâmetros no pack
funcao(pi, s); // 1 parâmetro no pack
funcao("hi"); // pack vazio
}
O compilador irá instancia 4 tipos de funções:
void funcao(const int&, const string&, const int&, const double&);
void funcao(const string&, const int&, const char(&)[3]);
void funcao(const double&, const string&);
void funcao(const char(&)[3]);
Operador sizeof...
Quando precisamos saber quantos elementos existem no pack, utilizamos o operador sizeof... (repare a elipsis). Este operador retorna uma expressão constante e não avalia o argumento.
template<typename ... Args>
void g(Args ... args) {
cout << sizeof...(Args) << endl; // número de parâmetros
cout << sizeof...(args) << endl; // número de parâmetros da função
}
Para acessar os parâmetros, precisamos expandir o pack. Para expandir um pack colocamos elipsis ao lado do padrão que queremos expandir e uma das formas é usarmos recursão para acessar os parâmetros, precisamos apenas informar uma condição de parada.
#include <iostream>
using namespace std;
// Condição de parada
template <typename T, typename... Args>
void funcao(const T& t)
{
cout << t << endl;
}
// Função será chamada recursivamente, até a condição de parada (quando rest for vazio)
template <typename T, typename... Args>
void funcao(const T& t, const Args& ... rest)
{
cout << t << endl;
funcao(rest...);
}
int main()
{
int i = 0;
double pi = 3.14;
string s = "Yellow submarine!";
funcao(i, s, 42, pi); // 3 parâmetros no pack
}
0
Yellow submarine!
42
3.14
Concepts e contraints
Na versão foi introduzido concepts e constraints. Constraints especificam requisitos para um argumento template. Um conjunto de constraints é denominado concept.
template <WrappableType T>
class wrapper { /* ... */ };
template <WrappableType T = int>
class wrapper { /* ... */ };
template <WrappableType... T>
class wrapper { /* ... */ };
Será visto nos próximos artigos
Parâmetros non-type template
Parâmetros non-type são aqueles que geralmente se espera quando se chama uma função ou método, ou seja: constantes, ponteiros de função ou objetos de linkagem externa ou, até mesmo, ponteiros para funções estáticas de classes, referências, std::nullptr_t, auto, auto&, and auto*[1:1][3].
- Tipos integrais
- Tipos de ponto flutuante
- Enumerações
- Ponteiros
- Ponteiros para funções membros
- Referências para l-values
- Classes, desde que:
- Todas as classes bases sejam públicas e não-mutáveis
- Todos as variáveis membros sejam non-mutable e não sejam estáticas
- Todos os tipos de todas as classes bases e variáveios membros sejam tipos estruturais ou arrays
A principal vantagem em usar parâmetros non-type no template em relação ao construtor são que os valores são conhecidos ainda antes do código ser compilado[3:1].
#include <iostream>
using namespace std;
template <typename T, size_t comprimento, size_t altura, size_t largura>
class Malote
{
public:
Malote();
T volume() const ;
T getAltura() const;
};
template <typename T, size_t comprimento, size_t altura, size_t largura>
Malote<T, comprimento, altura, largura>::Malote()
{
}
template <typename T, size_t comprimento, size_t altura, size_t largura>
T Malote<T, comprimento, altura, largura>::volume() const
{
return comprimento * altura * largura;
}
template <typename T, size_t comprimento, size_t altura, size_t largura>
T Malote<T, comprimento, altura, largura>::getAltura() const
{
return altura;
}
int main()
{
size_t largura = 6;
const size_t clargura = 6;
constexpr size_t cexplargura = 6;
// se descomentar o código abaixo gera erro de compilação
// Malote<int, 2, 3, largura> malote1; // Erro !
Malote<int, 2, 2, clargura> malote2; // Ok!
Malote<int, 2, 3, cexplargura> malote3; // Ok!
Malote<int, 2, 2, cexplargura> malote4; // Ok!
cout << "Volume malote 2:" << malote2.volume() << endl;
cout << "Volume malote 3:" << malote3.volume() << endl;
cout << "Altura malote 2:" << malote2.getAltura() << endl;
// malote3 = malote4; // Erro de compilação!
}
Volume malote 2: 36
Volume malote 3: 36
Altura malote 2: 3
A primeira restrição é que a linha abaixo gera erro de compilação se descomentada, pois largura precisa ser constante (const ou constexpr).
Malote<int, 2, 3, largura> malote1;
Uma segunda restrição é que como largura, altura e comprimento são parâmetros do template, eles passam a ser tipos diferentes se um dos valores for diferente.
Malote<int, 2, 3, cexplargura> malote3;
Malote<int, 2, 2, cexplargura> malote4;
malote3 = malote4; // Erro de compilação!
Atente-se que malote3 e malote4 não tem apenas valores diferentes. Eles são tipos diferentes ! Você não pode atribuir o valor de malote4 no malote3, pois geraria erro de compilação. Verifique com exemplo abaixo
static_assertis_same_v<decltype(malote3), decltype(malote4)>;
O código acima produz erro, pois malote3 e malote4 são tipos diferentes.

Portanto, tenha atenção que a maior parte dos compiladores interpretam os parâmetros int e long como tipos diferentes. Isso pode gerar code bloat pois seriam instanciados templates de int e long, ainda que a representação na memória fosse a mesma.
Caso se depare com este problema, a solução é incorporá-lo no construtor da classe e nas variáveis membros [4].
int main()
{
Malote<int> malote7; // Ok!
Malote<long> malote8; // Ok!
malote7 = malote8; //Erro, no VSStudio são tipos diferentes
}
Neste exemplo, malote7 e malote8 são instanciados como tipos diferentes pelo Microsoft Visual Studio.
Valores default (padrão)
Você também pode atribuir valores padrão. Desta forma é possível chamar a classe até sem valor algum como em malote4. Entretanto, seria necessário colocar um <> vazio.
// DEFINIÇÃO DA FUNÇÃO TEMPLATE
template <typename T=int, size_t comprimento=2, size_t altura=3, size_t largura=4>
class Malote
{
public:
Malote();
T volume() const ;
T getAltura() const;
};
// .. Código omitido
int main()
{
// DECLARÇÃO DA CLASSE TEMPLATE
Malote<> malote4; // Ok!
//Malote malote5; // Erro de compilação!
Malote<int> malote6; // Ok!
Malote<int, 2, 2> malote7; // Ok!
cout << "Volume malote: " << malote4.volume() << endl;
cout << "Volume malote: " << malote6.volume() << endl;
cout << "Volume malote: " << malote7.volume() << endl;
}
Volume malote: 24
Volume malote: 24
Volume malote: 16
Definição de um template de função (ou de classe) é diferente de declaração de um template de função (ou classe).
Definição de uma função ou classe é onde será feito o protótipo, o modelo que será utilizado pela declaração.
Veja o exemplo abaixo:
template <typename T> // ⬅️ Definição !
class A
{
public:
T t;
};
int main()
{
A<int> a; // ⬅️ Declaração!
}
Existem algumas regras que precisam ser seguidas quando usamos argumentos default para parâmetros templates[5]:
- A ordem importa.
- As regras para definição de parâmetros templates são as mesmas para parâmetros de chamadas de função.
- As regras para declaração são que eles podem ser suprimidos da ⬅️ direita para esquerda
- Se um parâmetro tiver valor default, então, todos os outros após precisam ser também argumentos default.
Template auto
Na versão C++17 foi introduzido o conceito de template auto.
O tipo do template é automaticamente deduzido e se for um tipo não permitido, será gerado um erro.[1:2]
//Definição
template <auto x>
struct foo
{ /* … */ };
foo<42> f1; // foo<int>
foo<42.0> f2; // foo<double> in C++20, error for older
// versions
foo<"42"> f3; // error
// Declaração
A última linha deste código produz erro, pois, por alguma razão obscura literais strings ainda não podem ser usados como argumentos para templates non-type.[1:3]
No próximo artigo veremos parâmetros template template.
Referências
BANCILA, M. TEMPLATE METAPROGRAMMING WITH C++ unlock the power of template metaprogramming to write... robust and efficient programs. S.l.: PACKT PUBLISHING LIMITED, 2022. ]: ↩︎ ↩︎ ↩︎ ↩︎
LIPPMAN, S. B.; LAJOIE, JOSÉE.; MOO, B. E. C++ primer. 5th ed ed. Upper Saddle River, N.J.: Addison-Wesley, 2013. ]: ↩︎
GREGOIRE, M. Professional C++. 5. ed. Indianapolis: John Wiley and Sons, 2020. ]: ↩︎ ↩︎
MEYERS, S. Effective C++: 55 specific ways to improve your programs and designs. 3rd ed ed. Upper Saddle River, NJ: Addison-Wesley, 2005. ]: ↩︎
GRIGORYAN, V.; WU, S. Expert C++: become a profiecient programmer by learning coding best practices with C++17 and C++20’s latest features. Birmingham: Packt Publishing, Limited, 2020. ]: ↩︎