Skip to content

Latest commit

 

History

History
234 lines (151 loc) · 12.8 KB

File metadata and controls

234 lines (151 loc) · 12.8 KB

🔝 Retour au Sommaire

3.1.3 — Quand utiliser auto vs type explicite

Chapitre 3 — Types, Variables et Opérateurs · Section 3.1 · Sous-section 3 sur 3
Prérequis : 3.1.1 — auto, 3.1.2 — decltype


Introduction

Vous connaissez maintenant les mécanismes de auto et decltype. La question qui se pose naturellement est : quand faut-il utiliser auto et quand faut-il écrire le type explicitement ?

Il n'existe pas de réponse universelle. La communauté C++ est partagée entre deux écoles de pensée, et la plupart des projets professionnels adoptent un compromis pragmatique entre les deux. L'objectif de cette sous-section est de vous présenter les deux approches, d'en extraire les principes communs, et de vous donner des heuristiques de décision applicables au quotidien.


Deux philosophies, un même objectif

L'approche « Almost Always Auto » (AAA)

Popularisée par Herb Sutter (ancien président du comité de standardisation C++) et défendue par des auteurs comme Scott Meyers, cette approche recommande d'utiliser auto par défaut et de ne revenir au type explicite que dans les rares cas où auto nuit à la lisibilité ou introduit un bug (proxy objects, par exemple).

Les arguments en faveur de cette approche sont les suivants :

  • Cohérence. Si auto est la règle, le lecteur sait que chaque type explicite a été écrit intentionnellement, ce qui attire l'attention là où elle est nécessaire.
  • Robustesse au refactoring. Si le type de retour d'une fonction change, les variables auto s'adaptent automatiquement sans nécessiter de modifications en cascade.
  • Pas de variables non initialisées. auto exige un initialiseur, ce qui élimine une catégorie entière de bugs (utilisation de variables non initialisées).
  • Pas de conversions implicites accidentelles. Avec un type explicite, le compilateur peut insérer une conversion silencieuse. Avec auto, le type est exactement celui de l'expression — aucune conversion n'a lieu.

Exemple typique du style AAA :

auto name = std::string{"Alice"};  
auto age = 30;  
auto scores = std::vector<int>{95, 87, 92};  
auto it = scores.begin();  
auto size = scores.size();  

L'approche conservatrice : « type explicite par défaut »

D'autres guides de style, comme le Google C++ Style Guide, adoptent une position plus prudente : utiliser auto uniquement lorsque le type est évident d'après le contexte immédiat, et préférer le type explicite dans les autres cas.

Les arguments en faveur de cette approche sont les suivants :

  • Lisibilité immédiate. Le type explicite documente la variable directement dans le code. Le lecteur n'a pas besoin de connaître le type de retour d'une fonction pour comprendre ce qu'une variable contient.
  • Revue de code facilitée. Dans un diff ou une revue de code sur une plateforme web, on ne dispose pas toujours d'un IDE capable d'afficher le type déduit au survol. Un type explicite rend le code auto-suffisant.
  • Intentions claires. Écrire double ratio = compute_ratio(a, b); plutôt que auto ratio = compute_ratio(a, b); affirme que le développeur attend un double et que toute modification du type de retour devrait être un signal d'alerte.

Exemple typique du style conservateur :

std::string name = "Alice";  
int age = 30;  
std::vector<int> scores = {95, 87, 92};  
auto it = scores.begin(); // auto accepté ici : le type est évident et long  
std::size_t size = scores.size();  

Ce que les deux approches partagent

Malgré leurs différences, les deux écoles s'accordent sur plusieurs points fondamentaux :

  • auto est toujours approprié pour les itérateurs, les résultats de make_unique/make_shared, les casts explicites et les structured bindings.
  • auto est toujours approprié dans les boucles range-based (const auto&).
  • Un type explicite est toujours préférable lorsque auto déduirait un type proxy ou un type inattendu.
  • La cohérence au sein d'un projet est plus importante que le choix de l'une ou l'autre approche.

Heuristiques de décision

Plutôt que d'adhérer dogmatiquement à l'une des deux philosophies, voici un ensemble d'heuristiques concrètes qui fonctionnent dans la majorité des contextes professionnels.

Utilisez auto quand le type est visible dans la même ligne

Si l'expression d'initialisation révèle le type sans ambiguïté, auto ne sacrifie aucune lisibilité :

// Le type est dans l'expression — auto est parfaitement clair
auto ptr = std::make_unique<DatabaseConnection>(config);  
auto count = static_cast<int>(raw_count);  
auto widget = Widget{param1, param2};  
auto it = container.begin();  
auto [key, value] = *map_iterator; // structured binding  

Dans chacun de ces cas, écrire le type explicite n'apporterait aucune information supplémentaire au lecteur — il serait une simple répétition.

Utilisez auto quand le type est long et secondaire

Certains types sont des détails d'implémentation que le lecteur n'a pas besoin de voir. Les itérateurs en sont l'exemple canonique, mais ce n'est pas le seul cas :

// Le type exact de l'itérateur n'apporte rien à la compréhension
auto it = std::find_if(users.begin(), users.end(),
    [](const auto& u) { return u.is_active(); });

// Le type de retour de chrono est verbeux et secondaire
auto start = std::chrono::steady_clock::now();

// Le type du lock est un détail technique
auto lock = std::scoped_lock{mutex_a, mutex_b};

Préférez un type explicite quand il documente une intention

Quand le type n'est pas visible dans l'expression et qu'il porte une signification importante pour la compréhension du code, le rendre explicite agit comme une forme de documentation :

// Le lecteur sait immédiatement que ratio est un flottant
double ratio = compute_compression_ratio(original, compressed);

// L'intention est claire : on attend une durée en millisecondes
std::chrono::milliseconds timeout = get_default_timeout();

// Le type explicite signale que parse_config retourne un objet Config
Config config = parse_config("/etc/app.yaml");

Avec auto, le lecteur devrait connaître la signature de chaque fonction pour reconstituer mentalement le type. Dans une base de code large, c'est une charge cognitive non négligeable.

Préférez un type explicite pour les types numériques simples

Les littéraux numériques en C++ ont des types par défaut qui ne correspondent pas toujours à l'intention du développeur. Un type explicite élimine toute ambiguïté :

// Intention claire : on veut un entier 32 bits non signé
uint32_t port = 8080;

// Intention claire : on veut un flottant simple précision
float temperature = 36.6f;

// Avec auto, le type dépend du littéral — source d'erreurs subtiles
auto port = 8080;     // int (signé) — est-ce voulu ?  
auto temp = 36.6;     // double — pas float, est-ce voulu ?  

Pour les types numériques, l'explicitation du type joue un double rôle : elle documente l'intention et elle garantit la représentation mémoire souhaitée.

Utilisez toujours const auto& dans les boucles de lecture

C'est un cas sans débat — les deux écoles convergent :

for (const auto& item : collection) {
    process(item);
}

Cette forme est universellement recommandée pour itérer en lecture seule. Elle évite les copies, exprime l'intention de ne pas modifier les éléments, et s'adapte automatiquement au type du conteneur.

Pour les types primitifs légers (int, double, char), la copie est si peu coûteuse que const auto& et auto sont équivalents en performance. const auto& reste néanmoins préférable par cohérence.

Utilisez un type explicite face aux proxy objects

Comme vu en section 3.1.1, certains types de la bibliothèque standard retournent des objets proxy qui ne se comportent pas comme le type attendu. Dans ces cas, le type explicite n'est pas seulement préférable — il est nécessaire :

std::vector<bool> flags = {true, false, true};

// ❌ Dangereux : auto capture le proxy, pas un bool
auto flag = flags[0];

// ✅ Sûr : conversion explicite vers bool
bool flag = flags[0];

La même prudence s'applique aux bibliothèques d'algèbre linéaire (Eigen, Armadillo), aux expression templates, et à tout framework utilisant l'évaluation paresseuse (lazy evaluation).


Guide de référence rapide

Le tableau suivant synthétise les recommandations sous forme d'un guide de décision rapide.

Situation Recommandation Exemple
Type visible dans l'initialiseur auto auto p = std::make_unique<T>(...)
Itérateurs auto auto it = map.find(key)
Structured bindings auto auto [name, age] = get_person()
Boucle range-based (lecture) const auto& for (const auto& e : vec)
Boucle range-based (modification) auto& for (auto& e : vec)
Casts explicites auto auto n = static_cast<int>(x)
Résultat de make_unique/make_shared auto auto sp = std::make_shared<T>()
Type de retour non évident Type explicite Config cfg = load("path")
Types numériques avec intention précise Type explicite uint16_t port = 443
Proxy objects (vector<bool>, etc.) Type explicite bool b = flags[0]
Interface publique (header) Type explicite std::string get_name() const
Variable dont le type porte du sens métier Type explicite Timestamp created_at = now()

L'impact du tooling moderne

Un argument souvent avancé en faveur de auto est que les IDE modernes affichent le type déduit au survol de la variable (fonctionnalité inlay hints de clangd, IntelliSense de VS Code, ou encore CLion). Cela réduit considérablement le coût de lisibilité de auto pour le développeur qui travaille dans un environnement configuré.

Cependant, le code est lu dans de nombreux contextes où ces outils ne sont pas disponibles : revues de code sur GitHub/GitLab, lectures dans un terminal, documentation, extraits dans des messages ou des présentations. Un code qui reste lisible sans IDE est un code plus robuste sur le long terme.

La recommandation est donc de considérer le tooling comme un bonus, pas comme un prérequis. Si auto rend une ligne ambiguë sans IDE, préférez le type explicite.


Ce que dit le style guide de votre projet

En pratique, le choix entre auto et type explicite est souvent tranché par le style guide du projet sur lequel vous travaillez. Voici les positions des guides les plus influents :

C++ Core Guidelines (Stroustrup & Sutter) — Position AAA modérée. Recommandent auto lorsque le type est évident ou quand il s'agit de réduire la verbosité. Découragent auto lorsque le type n'est pas clair d'après le contexte. Règle ES.11 : « Use auto to avoid redundant repetition of type names. »

Google C++ Style Guide — Position conservatrice. Autorisent auto pour les itérateurs, les types longs, et les lambdas. Déconseillent auto lorsque le type n'est pas évident pour un lecteur ne connaissant pas l'API appelée. Le guide insiste sur la lisibilité dans les revues de code.

LLVM Coding Standards — Position modérée. Recommandent auto quand le type est évident (casts, constructeurs, littéraux typés) et quand il simplifie le code sans sacrifier la clarté.

Si votre projet ne dispose pas encore de style guide, les heuristiques présentées dans cette section constituent un point de départ raisonnable. L'important est d'adopter une convention et de s'y tenir — l'incohérence est plus nuisible que n'importe quel choix particulier.


En résumé

Le débat auto vs type explicite n'est pas une question de bien ou de mal — c'est une question d'équilibre entre concision et clarté. Les deux extrêmes (tout en auto ou jamais de auto) nuisent à la qualité du code pour des raisons différentes.

Les principes directeurs sont simples :

  • auto quand le type est évident, long ou secondaire. L'inférence réduit le bruit sans sacrifier la compréhension.
  • Type explicite quand le type porte du sens, n'est pas visible, ou protège contre un piège. L'explicitation documente l'intention et sécurise le code.
  • Cohérence au sein du projet avant tout. Suivez le style guide de votre équipe. En l'absence de guide, adoptez une position modérée et documentez-la.

Avec de la pratique, le choix devient intuitif. Vous apprendrez à « sentir » quand auto allège le code et quand il l'obscurcit — et cette sensibilité est l'un des marqueurs d'un développeur C++ expérimenté.


⏭️ Types primitifs, tailles et représentation mémoire