Capítulo 13: Templates Modernos, Cláusula Requires e C++20 Concepts
Capítulo 13: Templates Modernos, Cláusula Requires e C++20 Concepts
Especialização em Sistemas com C++ • C++20 & C++23 • RAII, Smart Pointers, Templates, Concorrência e Alta Performance
🗺️ Mapa Conceitual da Metaprogramação Moderna e Restrições com Concepts
flowchart TD
Templates["Programação Genérica em C++"] --> Tradicional["Templates Clássicos (SFINAE / std::enable_if)"]
Templates --> Moderno["C++20 Concepts (<concepts>)"]
subgraph TRADICIONAL_ISSUES["Problemas do C++ Clássico"]
ErrorDumps["Mensagens de Erro Gigantescas e Ilegíveis"]
ComplexSFINAE["Metaprogramação Arcana e Frágil"]
SlowCompilation["Tempos Excessivos de Compilação"]
end
subgraph CONCEPTS_ADV["Vantagens dos Concepts C++20"]
ExplicitConstraints["Contratos Explícitos na Assinatura (requires)"]
CleanErrors["Diagnósticos Claros ('constraints not satisfied')"]
OverloadSelection["Seleção de Sobrecarga Determinística"]
end
Tradicional --> TRADICIONAL_ISSUES
Moderno --> CONCEPTS_ADV
CONCEPTS_ADV --> ProductionCode["Código Genérico Seguro e de Alta Performance"]
style Templates fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style TRADICIONAL_ISSUES fill:#ffebee,stroke:#f44336,stroke-width:2px
style CONCEPTS_ADV fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
🏛️ 1. O Fim da “Idade das Trevas” dos Templates: Adeus ao SFINAE
Historicamente, os templates em C++ eram baseados em tipagem estrutural tardia (duck typing em tempo de compilação): se uma função de template usasse a + b, qualquer tipo que passasse no compilador era aceito, mesmo que semanticamente fizesse zero sentido. Caso houvesse incompatibilidade, o erro só eclodia no fundo da árvore de instanciação do template, produzindo diagnósticos com centenas de linhas ilegíveis.
Com a chegada dos Concepts no C++20, a programação genérica ganhou contratos de tipo formais de primeira classe. Agora, o compilador valida se o argumento satisfaz o conceito antes mesmo de tentar instanciar o corpo do template.
⚙️ 2. Sintaxe de Declaração de Concepts e Expressões requires
Um concept é um predicado de tempo de compilação avaliado sobre tipos:
#include <concepts>
#include <string>
// 1. Definição de Concept com Requisitos de Expressão (requires-clause)
template <typename T>
concept SerializavelJSON = requires(T x) {
{ x.to_json() } -> std::same_as<std::string>; // Exige método retornando std::string
{ x.versao() } -> std::convertible_to<int>; // Exige método conversível para int
};
// 2. Três Sintaxes Equivalentes de Uso de Concepts:
// A) Sintaxe Estilo Terse (Mais elegante, idêntica a tipos normais):
void persistir(SerializavelJSON auto const& payload);
// B) Na Lista de Parâmetros de Template:
template <SerializavelJSON T>
void persistir(T const& payload);
// C) Com Cláusula 'requires' Explícita:
template <typename T>
requires SerializavelJSON<T>
void persistir(T const& payload);
📦 3. Despacho e Sobrecarga Baseados em Restrições (Subsumption)
Quando duas funções de template sobrecarregadas competem para atender a uma chamada, o compilador C++20 analisa a especificidade dos concepts (subsumption rules). Se um concept herda ou combina outros através de conjunção lógica (&&), o compilador seleciona automaticamente a implementação mais específica, sem conflito de ambiguidade:
template <typename T>
concept Drawable = requires(T a) { a.draw(); };
template <typename T>
concept ResizableDrawable = Drawable<T> && requires(T a) { a.resize(10, 10); };
// Chamada genérica
void render(Drawable auto const& d) {
d.draw();
}
// Sobrecarga mais especializada selecionada automaticamente quando o tipo suportar resize!
void render(ResizableDrawable auto const& d) {
d.resize(10, 10);
d.draw();
}
📊 4. Matriz Comparativa: SFINAE Clássico vs Concepts C++20
| Critério | SFINAE Clássico (enable_if_t) |
Concepts C++20 (concept) |
|---|---|---|
| Legibilidade do Código | Baixíssima (poluição de assinaturas com templates vazios) | Altíssima e expressiva |
| Clareza de Diagnóstico de Erro | Mensagens crípticas de dezenas de linhas | “Constraints not satisfied: type X lacks method Y” |
| Velocidade de Compilação | Lenta (múltiplas tentativas de substituição fracassadas) | Rápida (avaliação estática direta de predicados) |
| Integração com IDEs | Autocomplete limitado | Suporte total a IntelliSense e refatoração |