Comme le souligne Bjarne Stroustrup, « le C++ est un langage multi-paradigmes ». Il prend en charge de nombreux styles de programmation différents, ou paradigmes, et la programmation orientée objet n'en est qu'un parmi d'autres. Parmi les autres figurent la programmation structurée et la programmation générique. Au fil des ans, des experts C++ comme Andrei Alexandrescu, Scott Meyers et Herb Sutter ont promu l'utilisation de la programmation générique, souvent désignée sous le nom de Modern C++ Design.
Voici ce que dit Andrei Alexandrescu à propos du Modern C++ Design :
Modern C++ Design defines and systematically uses des composants génériques. - highly flexible design artifacts that are mixable and matchable to obtain rich behaviors with a small, orthogonal body of code.Trois aspects de son point de vue sont particulièrement intéressants :
- Modern C++ Design définit et utilise systématiquement des composants génériques..
- Une conception hautement flexible.
- Obtenir des comportements riches avec un corps de code petit et orthogonal.
En revanche, la POO est très populaire : l'héritage et le RTTI sont deux mécanismes puissants pour concevoir une application C++, et de nombreux développeurs préfèrent ce paradigme à l'approche de la programmation générique.
Voici une définition courante de l'héritage :
In object-oriented programming (OOP), l'héritage is when an object or class is based on another object (prototypal inheritance) or class (class-based inheritance), using the same implementation (inheriting from an object or class) specifying implementation to maintain the same behavior (realizing an interface; inheriting behavior). It is a mechanism for code reuse and to allow independent extensions of the original software via public classes and interfaces.De nombreux experts C++ recommandent d'éviter l'usage excessif de l'héritage et du polymorphisme dynamique. Après tout, quel est le problème avec l'héritage ?
Réponse courte: Un couplage très élevé.
Prenons comme exemple l'implémentation d'une classe de calcul de taxes.

CTaxCalculator ne collabore qu'avec des classes héritant de ICalculator et ne peut utiliser aucune autre classe non ICalculator, même si elle pourrait aider à calculer la taxe. La classe d'implémentation du calculateur sera fortement couplée à ICalculator, sans aucun moyen d'utiliser un autre type de classe, sauf à introduire des classes et interfaces supplémentaires pour contourner cette limitation. Par exemple, le pattern adaptateur est une solution pour contourner le couplage fort causé par l'héritage. Réfléchissez-y : certains design patterns du GoF existent précisément pour résoudre les problèmes causés par le couplage fort introduit par l'héritage.
La programmation générique à la rescousse
Avec la programmation générique, le même calculateur de taxes pourrait être implémenté comme ceci :

La classe CGenericTaxCalculator, elle, calcule la taxe en utilisant n'importe quel type capable de calculer la taxe, sans avoir besoin de connaître son type concret. Ce qui compte, ce sont les méthodes implémentées par le type, pas la classe spécifique à laquelle il appartient. C'est ce qui rend la programmation générique plus naturelle et plus flexible. En effet, dans la première implémentation, c'est comme dans le monde réel : une entreprise qui cherche un développeur n'accepte que les diplômés d'une école spécifique et rejette tous les autres même s'ils ont les compétences requises. Mais cette flexibilité a un prix : le code peut devenir plus difficile à comprendre. En POO, je peux simplement aller voir la définition de ICalculator pour savoir ce qui est attendu de ce type. En revanche, avec l'approche de la programmation générique, il peut être difficile de savoir exactement ce qui est attendu du paramètre template : quels membres doit-il contenir ? Quelles contraintes doivent être satisfaites ?
Les concepts de C++20 à la rescousse
Voici une courte description des concepts de C++20 :
Les concepts are an extension to C++'s templates, published as an ISO Technical Specification ISO/IEC TS 19217:2015.[1] They are named boolean predicates on template parameters, evaluated at compile time. A concept may be associated with a template (class template, function template, or member function of a class template), in which case it serves as a contrainte: it limits the set of arguments that are accepted as template parameters.La fonctionnalité des concepts a été reportée plusieurs fois. La bonne nouvelle, c'est que C++20 inclura cette fonctionnalité intéressante.
On trouve dans cet intéressant document la motivation derrière la fonctionnalité des concepts :
The intent of concepts is to model semantic categories (Number, Range, RegularFunction) rather than syntactic restrictions (HasPlus, Array). According to Règle fondamentale ISO C++ T.20, "The ability to specify a meaningful semantics is a defining characteristic of a true concept, as opposed to a syntactic constraint."Avec les concepts, nous pouvons résoudre le problème de la spécification des contraintes pour rendre le code plus lisible et plus maintenable. Il est vrai que Boost fournit une implémentation des concepts depuis de nombreuses années, mais les ajouter au langage rendra le C++ plus puissant et plus unique.
Un autre inconvénient bien connu de la programmation générique, ce sont ses messages d'erreur. Parfois, on ne comprend pas facilement pourquoi une erreur est signalée par le compilateur, et l'on peut perdre du temps à essayer de comprendre exactement pourquoi le code ne fonctionne pas comme prévu.
Heureusement, la fonctionnalité des concepts améliorera aussi les messages d'erreur, comme expliqué ici.
Résumé
Si l'héritage est surutilisé lorsqu'on choisit l'approche POO, il introduit un couplage élevé entre les classes et vous oblige à ajouter d'autres classes juste pour résoudre ce problème — ce qui n'est pas le cas lorsque vous choisissez d'adopter l'approche de la programmation générique. Et heureusement, la pièce manquante pour implémenter du code lisible, maintenable et faiblement couplé fera bientôt partie du langage.
