C++ Templates de Função
A Ana escreveu uma função maior que devolve o maior de dois números inteiros. Depois precisou da mesma coisa para preços (double) e para nomes (std::string). Copiar a função três vezes, trocando só o tipo, funciona, mas é chato e espalha erros. Um template (em português, "modelo" ou "molde") resolve isso: você escreve a função uma vez, com um tipo "em branco", e o compilador cria sozinho uma versão para cada tipo que você usar.
- Por que escrever a mesma função para
int,doubleestd::stringé um problema. - Como escrever um template de função:
template <typename T> T maior(T a, T b). - Como o compilador deduz o tipo
Tolhando os argumentos. - Como escolher o tipo à mão:
maior<double>(3, 4.5). - O erro
deduced conflicting types, quando os dois argumentos têm tipos diferentes. - Como ler um erro de template enorme sem pânico: procure a linha
required from here. - O atalho do C++20:
autonos parâmetros.
Todos os programas desta aula foram compilados com o g++ 15.2 na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/cpp, com o comando padrão do guia: g++ -std=c++23 -Wall -Wextra arquivo.cpp -o programa (veja Compilar com g++). Antes desta aula, é bom conhecer Funções e Sobrecarga de Funções.
A ideia: uma forma de biscoito
Pense numa forma de biscoito em formato de estrela. A forma não é um biscoito: é um molde. Com a mesma forma você faz biscoito de chocolate, de coco, de laranja. Muda a massa; o formato é o mesmo.
Um template de função é a forma. O "formato" é o código da função (comparar dois valores e devolver o maior). A "massa" é o tipo: int, double, std::string. Quando você usa o template com inteiros, o compilador "assa" uma função de verdade para inteiros. Quando usa com textos, assa outra, para textos.
| Na cozinha | No C++ |
|---|---|
| A forma de estrela | O template: template <typename T> T maior(T a, T b) |
| A massa escolhida | O tipo que entra no lugar de T: int, double… |
| O biscoito pronto | A função de verdade que o compilador gera: maior<int>, maior<double>… |
| Assar | Instanciar: o compilador cria a função na hora em que você a usa |
1. O problema: a mesma função três vezes
Sem templates, a única saída é a sobrecarga: três funções com o mesmo nome e tipos diferentes.
#include <iostream>
#include <string>
int maior(int a, int b) {
return a > b ? a : b;
}
double maior(double a, double b) {
return a > b ? a : b;
}
std::string maior(std::string a, std::string b) {
return a > b ? a : b;
}
int main() {
std::cout << maior(3, 7) << '\n';
std::cout << maior(2.5, 1.5) << '\n';
std::cout << maior(std::string("Ana"), std::string("Bruno")) << '\n';
}
$ g++ -std=c++23 -Wall -Wextra sem-template.cpp -o sem-template $ ./sem-template 7 2.5 Bruno « "Bruno" vem depois de "Ana" no alfabeto: é o "maior"
O a > b ? a : b é o operador ternário: "se a for maior que b, o resultado é a; senão, é b". Funciona. Mas olhe as três funções: o corpo é idêntico, só o tipo muda. E se amanhã você achar um erro nesse corpo, precisa consertar em três lugares (e vai esquecer um). Se precisar de long, char e float, são mais três cópias.
2. O primeiro template
Agora a mesma coisa com um template: uma função só, com um tipo "em branco" chamado T.
#include <iostream>
#include <string>
template <typename T>
T maior(T a, T b) {
return a > b ? a : b;
}
int main() {
std::cout << maior(3, 7) << '\n';
std::cout << maior(2.5, 1.5) << '\n';
std::cout << maior(std::string("Ana"), std::string("Bruno")) << '\n';
std::cout << maior('a', 'z') << '\n';
}
- template
- "Modelo". Avisa ao compilador: o que vem logo abaixo não é uma função comum, é um molde de função.
- <typename T>
- A lista de "espaços em branco", entre
<e>.typenamequer dizer "nome de tipo":Tvai ser um tipo (int,double…). EsseTé o parâmetro de template. - T
- Um nome qualquer, como o de uma variável. A tradição é usar
T(de type, "tipo"), masTipotambém valeria. - T maior(T a, T b)
- Uma função normal, só que com
Tonde antes estavaint: recebe dois valores do tipoTe devolve umT. - return a > b ? a : b;
- O mesmo corpo de antes. Ele funciona para qualquer tipo que saiba fazer
>. - maior(3, 7)
- A chamada não muda nada: você usa o template como uma função comum.
$ g++ -std=c++23 -Wall -Wextra maior.cpp -o maior $ ./maior 7 2.5 Bruno z « o template serviu até para char, que nem estava nos planos
Uma função escrita, quatro tipos atendidos. Se houver um erro no corpo, você conserta num lugar só.
typename ou class?
Você vai encontrar muito código escrito template <class T>. Aqui as duas palavras são a mesma coisa: class nesse lugar não quer dizer que T precisa ser uma classe (o int serve). O próprio g++ escreve class nas mensagens de erro, como você vai ver adiante. Este guia usa typename, que diz melhor o que é.
O compilador realmente cria uma função para cada tipo
O template em si não vira código de máquina. Na hora de compilar, o g++ vê cada chamada, descobre o tipo e gera (instancia) uma função de verdade. Dá para ver isso no programa pronto com o nm, uma ferramenta que lista os nomes das funções dentro de um executável (-C deixa os nomes legíveis):
$ nm -C maior | grep maior 0000000000002d66 W char maior<char>(char, char) 0000000000002af3 W double maior<double>(double, double) 0000000000002ad3 W int maior<int>(int, int) 0000000000002cee W std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > maior<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)
Quatro funções, uma por tipo usado: maior<char>, maior<double>, maior<int> e uma para texto. A última tem um nome assustador porque std::string, por dentro, se chama std::basic_string<char, …>: ela mesma é um template (aula Templates de Classe). Guarde essa imagem: nomes enormes assim aparecem nas mensagens de erro, e não são culpa sua.
3. Dedução: o compilador descobre o T
Em maior(3, 7) você não disse qual é o T. O compilador olhou os argumentos e deduziu: 3 é int, 7 é int, então T = int. Isso se chama dedução de argumentos de template.
| Chamada | Tipo dos argumentos | T deduzido | Função gerada |
|---|---|---|---|
maior(3, 7) | int, int | int | int maior(int, int) |
maior(2.5, 1.5) | double, double | double | double maior(double, double) |
maior('a', 'z') | char, char | char | char maior(char, char) |
maior(std::string("Ana"), std::string("Bruno")) | std::string, std::string | std::string | std::string maior(std::string, std::string) |
std::string("Ana") e não só "Ana"?
Um texto entre aspas, sozinho, não é um std::string: é um array de char herdado do C (aula Strings). Com maior("Ana", "Bruno"), o T seria deduzido como const char* (um ponteiro), e o > compararia endereços de memória, não letras. Compila, mas o resultado depende de onde os textos foram parar na memória. Por isso o exemplo transforma os textos em std::string antes.
Experimente: rode como está. Depois acrescente uma chamada com dois long, como maior(10L, 20L), e uma com dois nomes de pessoas da sua casa (lembre do std::string(...)). Você não precisa mexer no template.
#include <iostream>
#include <string>
template <typename T>
T maior(T a, T b) {
return a > b ? a : b;
}
int main() {
std::cout << maior(15, 8) << '\n';
std::cout << maior(4.99, 5.49) << '\n';
std::cout << maior(std::string("pera"), std::string("maçã")) << '\n';
}
4. Escolher o tipo à mão: maior<double>(…)
Você também pode dizer o tipo, em vez de deixar o compilador deduzir. Escreva o tipo entre < e > logo depois do nome da função:
#include <iostream>
template <typename T>
T maior(T a, T b) {
return a > b ? a : b;
}
int main() {
std::cout << maior<double>(3, 4.5) << '\n';
std::cout << maior<int>(3, 4.5) << '\n';
std::cout << maior(3.0, 4.5) << '\n';
}
$ g++ -std=c++23 -Wall -Wextra explicito.cpp -o explicito $ ./explicito 4.5 4 « maior<int>: o 4.5 virou 4 antes da comparação 4.5
| Linha | O que acontece |
|---|---|
maior<double>(3, 4.5) | Você mandou: T = double. Não há dedução. O 3 é convertido para 3.0, e o resultado é 4.5. |
maior<int>(3, 4.5) | T = int. O 4.5 é convertido para int e perde a parte depois da vírgula: vira 4. O resultado é 4. Repare: nenhum aviso, nem com -Wall -Wextra. |
maior(3.0, 4.5) | Outra forma de resolver: escrever o 3 como 3.0. Agora os dois são double e a dedução funciona. |
Quando você escreve o tipo, a função passa a se comportar como uma função comum daquele tipo: os argumentos são convertidos se precisar. Isso é útil, mas exige cuidado com conversões que perdem informação, como double → int.
5. Argumentos de tipos diferentes: deduced conflicting types
E se você deixar o compilador deduzir, mas os dois argumentos tiverem tipos diferentes?
#include <iostream>
template <typename T>
T maior(T a, T b) {
return a > b ? a : b;
}
int main() {
std::cout << maior(3, 4.5) << '\n';
}
$ g++ -std=c++23 -Wall -Wextra misturado.cpp -o misturado
misturado.cpp: In function ‘int main()’:
misturado.cpp:9:23: error: no matching function for call to ‘maior(int, double)’
9 | std::cout << maior(3, 4.5) << '\n';
| ~~~~~^~~~~~~~
misturado.cpp:9:23: note: there is 1 candidate
misturado.cpp:4:3: note: candidate 1: ‘template<class T> T maior(T, T)’
4 | T maior(T a, T b) {
| ^~~~~
misturado.cpp:4:3: note: template argument deduction/substitution failed:
misturado.cpp:9:23: note: deduced conflicting types for parameter ‘T’ (‘int’ and ‘double’)
9 | std::cout << maior(3, 4.5) << '\n';
| ~~~~~^~~~~~~~
Leia de cima para baixo (a estrutura arquivo:linha:coluna está em Lendo Erros do Compilador):
- error: no matching function for call to ‘maior(int, double)’
- "Nenhuma função combina com a chamada
maior(int, double)". O g++ mostra os tipos que você passou. - note: there is 1 candidate
- "Há 1 candidata": o compilador achou uma função chamada
maiore explica por que ela não serviu. - candidate 1: ‘template<class T> T maior(T, T)’
- A candidata é o seu template (o g++ escreve
classno lugar detypename). - template argument deduction/substitution failed
- "A dedução dos argumentos de template falhou."
- deduced conflicting types for parameter ‘T’ (‘int’ and ‘double’)
- O motivo: "deduzi tipos conflitantes para
T". Pelo3,Tseriaint; pelo4.5, seriadouble.Tsó pode ser um tipo, e o compilador não escolhe por você.
Repare que, na dedução, o compilador não converte nada: ele exige que os dois argumentos já tenham o mesmo tipo. Há três consertos:
| Conserto | Código | Quando usar |
|---|---|---|
| Dizer o tipo | maior<double>(3, 4.5) | Os valores vêm de variáveis de tipos diferentes e você sabe qual tipo quer. |
| Igualar os argumentos | maior(3.0, 4.5) ou maior(static_cast<double>(qtd), 4.5) | Você pode mudar o valor escrito, ou converter uma variável (aula Conversão de Tipos). |
| Aceitar dois tipos | auto maior(auto a, auto b) (seção 8) | Misturar tipos é normal no seu caso e o resultado pode ser do tipo "maior". |
Missão: rode e veja o erro deduced conflicting types (no editor, o Clang escreve deduced conflicting types for parameter 'T' ('int' vs. 'double')). Conserte só a linha do main, sem mexer no template, para o programa mostrar 4.5.
#include <iostream>
template <typename T>
T maior(T a, T b) {
return a > b ? a : b;
}
int main() {
std::cout << maior(3, 4.5) << '\n';
}
#include <iostream>
template <typename T>
T maior(T a, T b) {
return a > b ? a : b;
}
int main() {
std::cout << maior<double>(3, 4.5) << '\n';
}
6. Um tipo que não serve: required from here
O template promete funcionar para "qualquer T", mas o corpo dele usa a > b. E se o tipo não souber fazer >? Uma struct Produto, por exemplo, não sabe: você nunca disse o que é um produto "maior" que outro.
#include <iostream>
#include <string>
struct Produto {
std::string nome;
double preco;
};
template <typename T>
T maior(T a, T b) {
return a > b ? a : b;
}
int main() {
Produto p1{"caderno", 12.5};
Produto p2{"caneta", 3.0};
Produto m = maior(p1, p2);
std::cout << m.nome << '\n';
}
$ g++ -std=c++23 -Wall -Wextra produto.cpp -o produto
produto.cpp: In instantiation of ‘T maior(T, T) [with T = Produto]’:
produto.cpp:17:22: required from here
17 | Produto m = maior(p1, p2);
| ~~~~~^~~~~~~~
produto.cpp:11:14: error: no match for ‘operator>’ (operand types are ‘Produto’ and ‘Produto’)
11 | return a > b ? a : b;
| ~~^~~
- In instantiation of ‘T maior(T, T) [with T = Produto]’
- "Ao instanciar (assar)
maiorcomT = Produto…". A dedução funcionou (T = Produto); o problema apareceu depois, ao gerar a função. - produto.cpp:17:22: required from here
- "Exigido a partir daqui": a linha 17 do seu arquivo, a chamada que pediu essa versão do template. É a linha mais importante.
- produto.cpp:11:14: error: no match for ‘operator>’
- O erro em si, na linha 11 (dentro do template): "não há
operator>paraProdutoeProduto".operator>é o nome técnico do sinal>.
Repare na diferença: o erro aponta para dentro do template (linha 11), mas o template está certo. O culpado é quem chamou (linha 17), com um tipo que não serve. Os consertos possíveis: comparar o que interessa (maior(p1.preco, p2.preco)) ou ensinar o Produto a fazer > (aula Sobrecarga de Operadores).
7. O erro de template enorme (e como lê-lo)
No exemplo acima, o template era seu e pequeno, e o erro coube em sete linhas. Agora a Ana tenta a mesma coisa com o std::max, um template da biblioteca padrão (do cabeçalho <algorithm>) que faz exatamente o que o nosso maior faz:
#include <algorithm>
#include <iostream>
#include <string>
struct Produto {
std::string nome;
double preco;
};
int main() {
Produto p1{"caderno", 12.5};
Produto p2{"caneta", 3.0};
std::cout << std::max(p1, p2).nome << '\n';
}
Treze linhas de programa. Sessenta e uma linhas de erro, copiadas inteiras do terminal:
$ g++ -std=c++23 -Wall -Wextra max-produto.cpp -o max-produto
In file included from /usr/include/c++/15/algorithm:62,
from max-produto.cpp:1:
/usr/include/c++/15/bits/stl_algobase.h: In instantiation of ‘constexpr const _Tp& std::max(const _Tp&, const _Tp&) [with _Tp = Produto]’:
max-produto.cpp:13:26: required from here
13 | std::cout << std::max(p1, p2).nome << '\n';
| ~~~~~~~~^~~~~~~~
/usr/include/c++/15/bits/stl_algobase.h:263:15: error: no match for ‘operator<’ (operand types are ‘const Produto’ and ‘const Produto’)
263 | if (__a < __b)
| ~~~~^~~~~
/usr/include/c++/15/bits/stl_algobase.h:263:15: note: there are 7 candidates
In file included from /usr/include/c++/15/bits/stl_algobase.h:64:
/usr/include/c++/15/bits/stl_pair.h:1049:5: note: candidate 1: ‘template<class _T1, class _T2, class _U1, class _U2> constexpr std::common_comparison_category_t<decltype (std::__detail::__synth3way(declval<_T1&>(), declval<_U1&>())), decltype (std::__detail::__synth3way(declval<_T2&>(), declval<_U2&>()))> std::operator<=>(const pair<_T1, _T2>&, const pair<_U1, _U2>&)’ (reversed)
1049 | operator<=>(const pair<_T1, _T2>& __x, const pair<_U1, _U2>& __y)
| ^~~~~~~~
/usr/include/c++/15/bits/stl_pair.h:1049:5: note: template argument deduction/substitution failed:
/usr/include/c++/15/bits/stl_algobase.h:263:15: note: ‘const Produto’ is not derived from ‘const std::pair<_T1, _T2>’
263 | if (__a < __b)
| ~~~~^~~~~
In file included from /usr/include/c++/15/bits/stl_algobase.h:67:
/usr/include/c++/15/bits/stl_iterator.h:578:5: note: candidate 2: ‘template<class _IteratorL, class _IteratorR> requires three_way_comparable_with<_IteratorR, _IteratorL, std::partial_ordering> constexpr std::compare_three_way_result_t<_IteratorL, _IteratorR> std::operator<=>(const reverse_iterator<_IteratorL>&, const reverse_iterator<_IteratorR>&)’ (reversed)
578 | operator<=>(const reverse_iterator<_IteratorL>& __x,
| ^~~~~~~~
/usr/include/c++/15/bits/stl_iterator.h:578:5: note: template argument deduction/substitution failed:
/usr/include/c++/15/bits/stl_algobase.h:263:15: note: ‘const Produto’ is not derived from ‘const std::reverse_iterator<_IteratorL>’
263 | if (__a < __b)
| ~~~~^~~~~
/usr/include/c++/15/bits/stl_iterator.h:1674:5: note: candidate 3: ‘template<class _IteratorL, class _IteratorR> requires three_way_comparable_with<_IteratorR, _IteratorL, std::partial_ordering> constexpr std::compare_three_way_result_t<_IteratorL, _IteratorR> std::operator<=>(const move_iterator<_IteratorL>&, const move_iterator<_IteratorR>&)’ (reversed)
1674 | operator<=>(const move_iterator<_IteratorL>& __x,
| ^~~~~~~~
/usr/include/c++/15/bits/stl_iterator.h:1674:5: note: template argument deduction/substitution failed:
/usr/include/c++/15/bits/stl_algobase.h:263:15: note: ‘const Produto’ is not derived from ‘const std::move_iterator<_IteratorL>’
263 | if (__a < __b)
| ~~~~^~~~~
/usr/include/c++/15/bits/stl_iterator.h:596:5: note: candidate 4: ‘template<class _Iterator> requires three_way_comparable<_Iterator, std::partial_ordering> constexpr std::compare_three_way_result_t<_Iterator, _Iterator> std::operator<=>(const reverse_iterator<_IteratorL>&, const reverse_iterator<_IteratorL>&)’ (rewritten)
596 | operator<=>(const reverse_iterator<_Iterator>& __x,
| ^~~~~~~~
/usr/include/c++/15/bits/stl_iterator.h:596:5: note: template argument deduction/substitution failed:
/usr/include/c++/15/bits/stl_algobase.h:263:15: note: ‘const Produto’ is not derived from ‘const std::reverse_iterator<_IteratorL>’
263 | if (__a < __b)
| ~~~~^~~~~
/usr/include/c++/15/bits/stl_iterator.h:1741:5: note: candidate 5: ‘template<class _Iterator> requires three_way_comparable<_Iterator, std::partial_ordering> constexpr std::compare_three_way_result_t<_Iterator, _Iterator> std::operator<=>(const move_iterator<_IteratorL>&, const move_iterator<_IteratorL>&)’ (rewritten)
1741 | operator<=>(const move_iterator<_Iterator>& __x,
| ^~~~~~~~
/usr/include/c++/15/bits/stl_iterator.h:1741:5: note: template argument deduction/substitution failed:
/usr/include/c++/15/bits/stl_algobase.h:263:15: note: ‘const Produto’ is not derived from ‘const std::move_iterator<_IteratorL>’
263 | if (__a < __b)
| ~~~~^~~~~
/usr/include/c++/15/bits/stl_iterator.h:545:5: note: candidate 6: ‘template<class _IteratorL, class _IteratorR> constexpr bool std::operator<(const reverse_iterator<_IteratorL>&, const reverse_iterator<_IteratorR>&) requires requires{{std::operator<::__x->base() > std::operator<::__y->base()} -> decltype(auto) [requires std::convertible_to<<placeholder>, bool>];}’
545 | operator<(const reverse_iterator<_IteratorL>& __x,
| ^~~~~~~~
/usr/include/c++/15/bits/stl_iterator.h:545:5: note: template argument deduction/substitution failed:
/usr/include/c++/15/bits/stl_algobase.h:263:15: note: ‘const Produto’ is not derived from ‘const std::reverse_iterator<_IteratorL>’
263 | if (__a < __b)
| ~~~~^~~~~
/usr/include/c++/15/bits/stl_iterator.h:1689:5: note: candidate 7: ‘template<class _IteratorL, class _IteratorR> constexpr bool std::operator<(const move_iterator<_IteratorL>&, const move_iterator<_IteratorR>&) requires requires{{std::operator<::__x->base() < std::operator<::__y->base()} -> decltype(auto) [requires std::convertible_to<<placeholder>, bool>];}’
1689 | operator<(const move_iterator<_IteratorL>& __x,
| ^~~~~~~~
/usr/include/c++/15/bits/stl_iterator.h:1689:5: note: template argument deduction/substitution failed:
/usr/include/c++/15/bits/stl_algobase.h:263:15: note: ‘const Produto’ is not derived from ‘const std::move_iterator<_IteratorL>’
263 | if (__a < __b)
| ~~~~^~~~~
Assusta, e é normal: todo mundo que programa em C++ já levou um desses. O segredo é que você não precisa ler tudo. Siga estes passos:
- Procure a primeira linha com
required from here(use a busca do terminal ou do editor). É sempre uma linha do seu arquivo: aqui,max-produto.cpp:13:26: required from here. Logo abaixo, o g++ mostra a linha 13 e marca a chamadastd::max(p1, p2). É ali que você vai mexer. - Leia a primeira linha com
error:logo depois:no match for ‘operator<’ (operand types are ‘const Produto’ and ‘const Produto’). Tradução: "não sei fazer<com doisProduto". (Ostd::maxusa<, o nossomaiorusava>; a ideia é a mesma.) - Leia a linha
In instantiation of(a terceira) se quiser saber qual template e com qual tipo:std::maxcom_Tp = Produto. Nomes que começam com_(_Tp,__a) são nomes internos da biblioteca; leia_Tpcomo "o T deles". - Ignore o resto. As linhas
note: candidate 1atécandidate 7são o compilador contando todas as outras funções<que ele testou (parapair,reverse_iterator…) e por que nenhuma serviu. Quase nunca ajudam. - Ignore os caminhos
/usr/include/c++/15/…. São os arquivos da biblioteca padrão. O erro "acontece" lá dentro, mas a biblioteca está certa: o problema é o tipo que você mandou.
Resumindo a montanha de texto em uma frase: "na linha 13, você pediu o std::max de dois Produto, e Produto não sabe fazer <". É o mesmo erro da seção anterior, só que com um template maior.
O Clang escreve in instantiation of function template specialization 'std::max<Produto>' requested here no lugar de required from here, e põe o error: antes, com o texto invalid operands to binary expression ('const Produto' and 'const Produto') ("operandos inválidos"). A regra é a mesma: procure a linha que cita o seu arquivo. A aula Concepts mostra como o C++20 deixa essas mensagens bem mais curtas.
8. auto nos parâmetros C++20
Desde o C++20, existe um atalho: escrever auto no tipo de cada parâmetro. Isso se chama template de função abreviado: o compilador transforma a função num template sem você escrever a linha template <…>.
#include <iostream>
auto maior(auto a, auto b) {
return a > b ? a : b;
}
int main() {
std::cout << maior(3, 7) << '\n';
std::cout << maior(3, 4.5) << '\n';
std::cout << maior(10, 2.5) << '\n';
}
$ g++ -std=c++23 -Wall -Wextra abreviado.cpp -o abreviado $ ./abreviado 7 4.5 « maior(3, 4.5) agora compila! 10
- auto a, auto b
- Cada
autonum parâmetro vira um parâmetro de template separado. É como se você tivesse escritotemplate <typename T1, typename T2> auto maior(T1 a, T2 b). Por issomaior(3, 4.5)compila:aéintebédouble, cada um com seu tipo. - auto maior(…)
- O
autoantes do nome diz: "o tipo de retorno, descubra peloreturn" (veja auto). - a > b ? a : b
- Com um
inte umdouble, o ternário converte os dois paradouble. Por isso a função devolvedouble: emmaior(10, 2.5), o resultado é10.0(ocoutmostra10).
Se você esquecer o -std=c++20 (ou mais novo), o g++ 15, que usa C++17 por padrão, avisa:
$ g++ -std=c++17 -Wall -Wextra abreviado.cpp -o abreviado
abreviado.cpp:3:12: warning: use of ‘auto’ in parameter declaration only available with ‘-std=c++20’ or ‘-fconcepts’ [-Wc++20-extensions]
3 | auto maior(auto a, auto b) {
| ^~~~
abreviado.cpp:3:20: warning: use of ‘auto’ in parameter declaration only available with ‘-std=c++20’ or ‘-fconcepts’ [-Wc++20-extensions]
3 | auto maior(auto a, auto b) {
| ^~~~
"Uso de auto em declaração de parâmetro só disponível com -std=c++20". É só um aviso (o g++ compila assim mesmo, como extensão), mas outro compilador pode recusar. Use sempre o -std=c++23 do comando padrão.
9. Formas diferentes, e qual usar
| Forma | Exemplo | Os dois argumentos podem ter tipos diferentes? | Quando usar |
|---|---|---|---|
| Sobrecarga (uma função por tipo) | int maior(int, int) + double maior(double, double) | Não (cada uma tem os seus) | Quando o corpo muda de um tipo para outro. |
Template com typename T | template <typename T> T maior(T a, T b) | Não: os dois são T | O caso mais comum: mesmo corpo, e você quer que os dois sejam do mesmo tipo. |
Template com auto nos parâmetros C++20 | auto maior(auto a, auto b) | Sim: cada auto é um tipo | Funções curtas e genéricas, quando misturar tipos é aceitável. |
| Template com tipo escolhido na chamada | maior<double>(3, 4.5) | Sim, convertendo para o tipo escolhido | Quando a dedução falha ou dá um tipo que você não quer. |
Um detalhe de boa prática: para tipos "pesados" (std::string, std::vector), receba por referência constante, const T&, para não copiar à toa. Um template muito útil é um "mostrar" que serve para vetores de qualquer tipo:
#include <iostream>
#include <string>
#include <vector>
template <typename T>
void mostrar(const std::vector<T>& itens) {
for (const T& item : itens) {
std::cout << "- " << item << '\n';
}
std::cout << "(" << itens.size() << " itens)\n";
}
int main() {
std::vector<std::string> compras{"arroz", "feijão", "café"};
std::vector<int> notas{7, 9, 10};
mostrar(compras);
mostrar(notas);
}
$ g++ -std=c++23 -Wall -Wextra mostrar.cpp -o mostrar $ ./mostrar - arroz - feijão - café (3 itens) - 7 - 9 - 10 (3 itens)
Aqui o compilador deduziu T olhando dentro do argumento: compras é um std::vector<std::string>, então T = std::string. O void diz que a função não devolve nada (aula Retorno).
Quando dá errado
error: no matching function for call to ‘maior(int, double)’ … deduced conflicting types
O que significa: os argumentos têm tipos diferentes, e o template exige que os dois sejam o mesmo T.
Como consertar: diga o tipo (maior<double>(3, 4.5)), iguale os argumentos (3.0) ou use auto nos parâmetros.
In instantiation of … / required from here / error: no match for ‘operator>’
O que significa: o tipo deduzido não sabe fazer uma operação que o corpo do template usa (>, <, <<…).
Como consertar: vá até a linha do required from here: é a sua chamada. Passe um tipo que tenha a operação (por exemplo, p1.preco em vez de p1) ou ensine o tipo a fazê-la (Sobrecarga de Operadores).
error: ‘T’ does not name a type
$ g++ -std=c++23 -Wall -Wextra esqueci.cpp -o esqueci
esqueci.cpp:3:1: error: ‘T’ does not name a type
3 | T maior(T a, T b) {
| ^
esqueci.cpp: In function ‘int main()’:
esqueci.cpp:8:18: error: ‘maior’ was not declared in this scope
8 | std::cout << maior(3, 7) << '\n';
| ^~~~~
O que significa: "T não é nome de um tipo". Você esqueceu a linha template <typename T> antes da função, e o compilador não sabe o que é T. Como a função não pôde ser criada, o segundo erro (‘maior’ was not declared) é só consequência do primeiro. No Clang: unknown type name 'T'.
Como consertar: ponha template <typename T> na linha logo acima da função. Conserte sempre o primeiro erro e compile de novo.
O que significa: você escolheu um tipo inteiro (maior<int>(3, 4.5)) e o 4.5 virou 4 em silêncio.
Como consertar: escolha o tipo que guarda todos os valores (double). Se quiser que o compilador avise nesses casos, compile com -Wconversion (aula Avisos):
$ g++ -std=c++23 -Wall -Wextra -Wconversion explicito.cpp -o explicito
explicito.cpp: In function ‘int main()’:
explicito.cpp:10:32: warning: conversion from ‘double’ to ‘int’ changes value from ‘4.5e+0’ to ‘4’ [-Wfloat-conversion]
10 | std::cout << maior<int>(3, 4.5) << '\n';
| ^~~
"Conversão de double para int muda o valor de 4.5 para 4."
Referência rápida
| Escrita | O que faz |
|---|---|
template <typename T> | Começa um template com um parâmetro de tipo chamado T |
template <class T> | O mesmo que typename T |
template <typename T, typename U> | Template com dois tipos independentes |
maior(3, 7) | Chamada com dedução: T = int |
maior<double>(3, 4.5) | Chamada com o tipo escolhido: T = double |
auto f(auto a, auto b) C++20 | Template abreviado: cada auto é um tipo próprio |
const T& | Recebe sem copiar (bom para tipos grandes) |
required from here | Nas mensagens do g++: a linha do seu código que pediu o template |
Exercícios
1. A primeira linha
Complete a linha que transforma a função abaixo num template:
T dobro(T x) { return x * 2; }
2. Escolher o tipo
Complete para chamar maior com T = double:
3. Com template <typename T> T maior(T a, T b), o que acontece em maior(2, 9)?
4. Num erro de template de 60 linhas, qual linha procurar primeiro?
5. Por que auto maior(auto a, auto b) aceita maior(3, 4.5), mas template <typename T> T maior(T a, T b) não?
Missão final: o menor preço
Missão: escreva um template menor (o contrário do maior) com template <typename T> e use-o para mostrar o menor de 4 e 9, o menor de 2.5 e 1.5 e o menor entre os textos "pera" e "maçã" (como std::string). Saída esperada:
4 1.5 maçã
#include <iostream>
#include <string>
// escreva o template menor aqui
int main() {
// chame menor três vezes aqui
}
#include <iostream>
#include <string>
template <typename T>
T menor(T a, T b) {
return a < b ? a : b;
}
int main() {
std::cout << menor(4, 9) << '\n';
std::cout << menor(2.5, 1.5) << '\n';
std::cout << menor(std::string("pera"), std::string("maçã")) << '\n';
}
Resumo
- Um template de função é um molde: você escreve a função uma vez, com um tipo em branco (
T), e o compilador gera uma versão para cada tipo usado. - A forma:
template <typename T>na linha de cima, eTno lugar do tipo.class Té o mesmo quetypename T. - O compilador deduz
Tpelos argumentos. Para escolher à mão:maior<double>(3, 4.5). - Argumentos de tipos diferentes para o mesmo
Tdãodeduced conflicting types. - Se o tipo não tem a operação que o corpo usa, o erro aponta para dentro do template. Procure
required from here: é a sua linha. Ignore oscandidatee os caminhos da biblioteca. - No C++20,
auto f(auto a, auto b)é um template abreviado, com um tipo para cadaauto. - Próximo passo: moldes de classes, em Templates de Classe.