Templates de fonctions et déduction de type
Apprenez à écrire des fonctions qui fonctionnent avec n'importe quel type. Comprenez la déduction des arguments de template, les paramètres non-type et pourquoi les templates résident dans les en-têtes.
Pourquoi des templates de fonctions ?
Sans templates, écrire une fonction qui fonctionne pour plusieurs types implique de dupliquer la logique. Vous écririez int max(int, int), double max(double, double), std::string max(std::string, std::string) — une logique identique répétée pour chaque type. Les templates de fonctions vous permettent d'écrire l'algorithme une seule fois et de laisser le compilateur générer automatiquement les versions spécifiques à chaque type.
Un template de fonction n'est pas une fonction — c'est un plan de construction à partir duquel le compilateur instancie de vraies fonctions lorsque vous les utilisez avec des types précis. Tout cela se passe entièrement à la compilation et produit un code identique à celui que vous écririez à la main — sans aucun surcoût.
Syntaxe des templates
Un template de fonction commence par le mot-clé template suivi d'une liste de paramètres entre chevrons. Vous pouvez utiliser indifféremment typename ou class pour les paramètres de type — ils ont exactement la même signification. La convention moderne privilégie typename.
#include <iostream>
#include <string>
// 'typename' and 'class' are interchangeable here
template <typename T>
T maximum(T a, T b) {
return (a > b) ? a : b;
}
// Equivalent — 'class' means the same thing
template <class T>
T minimum(T a, T b) {
return (a < b) ? a : b;
}
int main() {
std::cout << maximum(3, 7) << '\n'; // deduces T = int
std::cout << maximum(3.14, 2.71) << '\n'; // deduces T = double
std::cout << maximum<std::string>("hello", "world") << '\n';
// explicit instantiation needed — "hello" is const char*, not std::string
}Règles de déduction des arguments de template
Le compilateur déduit les arguments de template en confrontant les types des arguments de la fonction aux motifs des paramètres. Les règles essentielles sont :
1. La correspondance exacte est privilégiée — maximum(3, 7) déduit T = int car les deux arguments sont des int.
2. Aucune conversion implicite pendant la déduction — maximum(3, 7.0) est une erreur : le compilateur voit T = int d'après le premier argument et T = double d'après le second, et refuse de choisir.
3. Décroissance tableau-vers-pointeur — lorsque T apparaît comme paramètre par valeur, les tableaux se dégradent en pointeurs. Utilisez T(&)[N] pour capturer le type et la taille du tableau.
4. Les const/volatile de premier niveau sont supprimés pour les paramètres par valeur, mais conservés pour les paramètres de type référence ou pointeur.
5. Fusion des références (reference collapsing) — si T est déduit comme un type référence (dans les références de transfert), les règles de fusion des références s'appliquent : T& & devient T&, T& && devient T&, T&& & devient T&, T&& && devient T&&.
Lorsque la déduction échoue ou est ambiguë, vous devez fournir les arguments de template explicitement : maximum.
Paramètres de template non-type
Les paramètres de template ne se limitent pas aux types. Les paramètres de template non-type (NTTP) acceptent des valeurs connues à la compilation, comme des entiers, des pointeurs ou (depuis C++20) des nombres à virgule flottante et des types classe. Ils sont très utiles pour intégrer des constantes de compilation dans le système de types.
#include <iostream>
#include <array>
#include <cstddef>
// Non-type parameter: N is a compile-time size_t value
template <typename T, std::size_t N>
T sum(const std::array<T, N>& arr) {
T total{};
for (const auto& elem : arr) {
total += elem;
}
return total;
}
// C++20: auto non-type parameter accepts any structural type
template <auto Value>
constexpr auto doubled = Value * 2;
int main() {
std::array<int, 4> nums = {1, 2, 3, 4};
std::cout << sum(nums) << '\n'; // 10, deduces T=int, N=4
std::cout << doubled<21> << '\n'; // 42
std::cout << doubled<3.14> << '\n'; // 6.28 (C++20)
}Chaque combinaison unique d'arguments de template pousse le compilateur à générer une instanciation distincte — une copie séparée de la fonction dans votre binaire. Appeler maximum, maximum et maximum produit trois fonctions. C'est généralement acceptable, mais dans les grandes bases de code, les templates peuvent contribuer au gonflement du code (code bloat : binaires plus gros, compilations plus longues). Pour l'atténuer, vous pouvez : utiliser des déclarations extern template pour contrôler où les instanciations ont lieu, extraire des templates le code indépendant du type, et recourir à l'effacement de type (type erasure) lorsque le polymorphisme à l'exécution est acceptable.
Comme le compilateur doit voir la définition complète du template pour l'instancier, les templates ne peuvent pas être répartis entre fichiers .h / .cpp comme les fonctions ordinaires. Si vous placez la définition d'un template dans un fichier .cpp et essayez de l'utiliser depuis une autre unité de traduction, vous obtiendrez une erreur d'édition de liens (référence indéfinie). La solution standard consiste à placer toute la définition du template dans le fichier d'en-tête. Si les temps de compilation deviennent problématiques, vous pouvez utiliser l'instanciation explicite : placez la définition dans un fichier .cpp et ajoutez template int maximum pour instancier explicitement les spécialisations dont vous avez besoin.
Instanciation explicite
Vous pouvez contrôler explicitement où et quand les templates sont instanciés. Dans les grands projets, cela peut réduire les temps de compilation en évitant les instanciations redondantes entre unités de traduction.
// ---- maximum.h ----
template <typename T>
T maximum(T a, T b) {
return (a > b) ? a : b;
}
// Suppress implicit instantiation in every TU that includes this header
extern template int maximum<int>(int, int);
extern template double maximum<double>(double, double);
// ---- maximum.cpp ----
#include "maximum.h"
// Explicit instantiation: generate code here, once
template int maximum<int>(int, int);
template double maximum<double>(double, double);- Les templates de fonctions sont des plans de construction — le compilateur génère des fonctions concrètes pour chaque ensemble d'arguments de template
typenameetclasssont interchangeables dans les listes de paramètres de template- La déduction des arguments de template n'effectue aucune conversion implicite — les déductions ambiguës sont des erreurs
- Les paramètres de template non-type intègrent des valeurs de compilation dans le système de types
- Les templates doivent être définis dans les en-têtes (ou utiliser l'instanciation explicite), car le compilateur a besoin de la définition complète pour les instancier
Quiz — Testez vos connaissances
(15 XP)1. Que se passe-t-il lorsque vous appelez `maximum(3, 7.0)` avec le template `template<typename T> T maximum(T a, T b)` ?
2. Pourquoi les définitions de templates doivent-elles généralement être placées dans des fichiers d'en-tête ?
3. À quoi sert `extern template int maximum<int>(int, int);` ?