Bonjour,

Le polymorphisme consiste à avoir une même interface pour différents types. On peut alors écrire un bout de code valide pour plusieurs types différents à la fois.

Dans l'article On Understanding Types, Data Abstraction, and Polymorphism publié par Luca Cardelli et Peter Wegner en décembre 1985, les différentes catégories de polymorphisme sont les suivantes (schéma page 4) :
Nom : varieties_of_polymorphism.jpg
Affichages : 740
Taille : 20,6 Ko
  • Le polymorphisme paramétré : on met le type en paramètre. En C++, il s'agit des templates.
  • Le polymorphisme par sous-typage, aussi appelé polymorphisme d'inclusion : on utilise l'héritage. Les classes dérivées sont des sous-types de la classe de base.
  • La surcharge.
  • Le polymorphisme de coercition : il s'agit des conversions implicites. Par exemple, en C++, quand un paramètre de fonction est de type std::string ou const std::string&, on peut lui passer un paramètre de type char*.


Remarque : Aujourd'hui, le polymorphisme de coercition (coercion polymorphism) est mentionné succinctement dans la page Polymorphism du wikipédia anglais mais pas dans la page Polymorphisme du Wikipédia français.

En C++, soit Type un type objet non constant. A mon sens, Type& est un sous-type de const Type& et Type* est un sous-type de const Type* : Type& et Type* ont accès aux opérations de lecture et d'écriture tandis que const Type& et const Type* n'ont accès qu'aux opérations de lecture.
Je suis alors tenté de qualifier cela de polymorphisme par sous-typage, mais toutes les sources que j'ai lues sur le polymorphisme par sous-typage ne parlent que d'héritage.

A quelle catégorie de polymorphisme correspond que fait que Type& soit un sous-type de const Type& ?
Faut-il étendre la définition du polymorphisme par sous-typage ?

Par anticipation, je réponds d'avance aux remarques suivantes :

  • Remarque : Si on crée deux fonctions void foo(Type&) et void foo(const Type&), alors la fonction foo appelée dépend du type de l'argument, mais il s'agit de surcharge, pas de polymorphisme par sous-typage.
  • Réponse : Certes, mais c'est pareil avec l'héritage : si on crée deux fonctions void foo(Base&) et void foo(Deriv&), alors la fonction appelée dépend du type statique de l'argument et il s'agit de surcharge. Donc cette remarque ne soulève pas une différence entre l'héritage et le fait que Type& soit un sous-type de const Type&.


  • Remarque : Si on crée une fonction void bar(const Type) et que l'on passe un argument de type Type&, une copie est faite et il s'agit de polymorphisme de coercition, pas de polymorphisme par sous-typage.
  • Réponse : Certes, mais c'est pareil avec l'héritage : si on crée une fonction void bar(Base) et que l'on passe un argument de type Deriv&, cela compile si le constructeur de recopie de Base est publique. On a alors un slicing et il s'agit de polymorphisme de coercition. En outre, dans cette remarque, on parle de const Type qui n'est ni un type référence, ni un type pointeur alors que je parlais de const Type& et de const Type*.


  • Remarque : Si on crée deux fonctions membres int Type::baz() const et std::string Type::baz(), puisque std::string n'est pas implicitement convertible en int, alors la propriété « ref.baz() est implicitement convertible en int » est vraie avec une référence de type const Type& mais fausse avec un référence de type Type& donc, d'un certain point de vue, Type& n'est pas exactement un sous-type const Type&.
  • Réponse : Certes, mais c'est pareil avec l'héritage : Si on crée deux fonctions membres non virtuelles int Base::baz() et std::string Deriv::baz(), alors il y a de la surcharge aussi et, en suivant la même logique, d'un certain point de vue, Deriv& n'est pas exactement un sous-type de Base&.