1. #1
    Membre chevronné
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    avril 2016
    Messages
    447
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : avril 2016
    Messages : 447
    Points : 1 960
    Points
    1 960

    Par défaut Catégories de polymorphisme : cas de la const-correctness.

    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 : 102
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&.

  2. #2
    Expert éminent
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    août 2003
    Messages
    5 139
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : août 2003
    Messages : 5 139
    Points : 9 983
    Points
    9 983

    Par défaut

    Pour moi on est plus dans la famille des ad'hoc: il n'y a pas infinité de types, mais juste 2.

    De plus, si je ne m'abuse, le polymorphisme est lié à l'expression considérée. Pas uniquement aux types impliqués. C'est pour cela que tes remarques sont importantes relativement à la surcharge et au cas de slicing/copie.

    Enfin, je range le passage d'un `T&` à un `f(T const&)` dans la famille des coercitions. Il y a bien une conversion implicite qui n'affecte pas le blob binaire, mais son interprétation car il est maintenant décoré avec un nouveau qualificateur.
    Blog|FAQ C++|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS|Bons livres sur le C++
    Les MP ne sont pas une hotline. Je ne réponds à aucune question technique par le biais de ce média. Et de toutes façons, ma BAL sur dvpz est pleine...

  3. #3
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Ingénieur systèmes embarqués
    Inscrit en
    juin 2009
    Messages
    3 543
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 30
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Ingénieur systèmes embarqués

    Informations forums :
    Inscription : juin 2009
    Messages : 3 543
    Points : 9 335
    Points
    9 335
    Billets dans le blog
    1

    Par défaut

    A mon sens, Type& est un sous-type de const Type&
    Est-ce vraiment un sous-type ? Le terme "sous-type" n'implique pas une notion d'héritage ?

  4. #4
    Membre chevronné
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    avril 2016
    Messages
    447
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : avril 2016
    Messages : 447
    Points : 1 960
    Points
    1 960

    Par défaut

    Citation Envoyé par Bktero Voir le message
    Le terme "sous-type" n'implique pas une notion d'héritage ?
    C'est l'objet du fil :
    Citation Envoyé par Pyramidev Voir le message
    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.
    Pour développer un peu plus, la const-correctness a pas mal de propriétés communes avec l'héritage.
    Imaginons les classes :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class Boulanger {
    public:
    	// Pas de fonctions virtuelles. C'est fait exprès.
    	void faireDuPain() const;
    private:
    	std::string nom{};
    };
     
    class BoulangerPatissier : public Boulanger {
    public:
    	void faireUnGateau() const;
    private:
    	// Pas de variables membres. C'est fait exprès.
    };
    • Quand on manipule un const Type&, on ne sait pas si l'objet référé a été défini comme constant ou non. De même, quand on manipule un Boulanger&, on ne sait pas s'il réfère un objet de type dynamique Boulanger ou bien BoulangerPatissier.
    • const Type et Type ont la même représentation en binaire. La seule différence, c'est que Type a accès aux fonctions en écriture. Dans mon exemple, il en va de même pour Boulanger et BoulangerPatissier. Ils ont la même représentation en binaire. La seule différence pour le compilateur, c'est que la fonction faireUnGateau est disponible pour un type mais pas l'autre.


    Citation Envoyé par Luc Hermitte Voir le message
    Enfin, je range le passage d'un `T&` à un `f(T const&)` dans la famille des coercitions. Il y a bien une conversion implicite qui n'affecte pas le blob binaire, mais son interprétation car il est maintenant décoré avec un nouveau qualificateur.
    Mais si on passe un BoulangerPatissier& à un f(Boulanger&), on a aussi une conversion implicite d'un type référence vers un autre. En plus, dans mon exemple, Boulanger et BoulangerPatissier ont la même représentation en binaire.

    ----------------------------------------------------------------------------------------------------

    A part ça, après avoir réfléchi davantage, je me suis aperçu que, si on essayait d'étendre la définition du polymorphisme par sous-typage, alors on risquerait de l'étendre à d'autres cas que celui de la conversion de Type& en const Type& et de Type* en const Type*.

    Par exemple, soit le code suivant qui utilise std::variant :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    #include <functional>
    #include <iostream>
    #include <string>
    #include <variant>
     
    class Entier {
    public:
    	explicit Entier(int valeur) : m_valeur{valeur} {}
    	int getValeur() const {return m_valeur;}
    	void doubler() {m_valeur += m_valeur;}
    private:
    	int m_valeur;
    };
     
    class Chaine {
    public:
    	explicit Chaine(std::string valeur) : m_valeur{valeur} {}
    	std::string getValeur() const {return m_valeur;}
    	void doubler() {m_valeur += m_valeur;}
    private:
    	std::string m_valeur;
    };
     
    class EntierOuChaineHandle {
    private:
    	std::variant<std::reference_wrapper<Entier>,
    	             std::reference_wrapper<Chaine>> m_variant;
    public:
    	// Conversions implicites. C'est volontaire.
    	EntierOuChaineHandle(Entier& param) : m_variant{std::ref(param)} {}
    	EntierOuChaineHandle(Chaine& param) : m_variant{std::ref(param)} {}
    	void doubler() {
    		auto lamdbaQuiDouble = [](auto& refWrapper) {refWrapper.get().doubler();};
    		std::visit(lamdbaQuiDouble, m_variant);
    	}
    };
     
    void quadrupler(EntierOuChaineHandle handle) {
    	handle.doubler();
    	handle.doubler();
    }
     
    int main()
    {
    	Entier entier{10};
    	Chaine chaine{"bla"};
    	quadrupler(entier);
    	quadrupler(chaine);
    	std::cout << "Entier quadruple : " << entier.getValeur() << '\n'; // affiche 40
    	std::cout << "Chaine quadruple : " << chaine.getValeur() << '\n'; // affiche blablablabla
    	return 0;
    }
    Dans ce code, tout se passe comme si Entier et Chaine dérivaient d'une même classe EntierOuChaine avec une fonction virtuelle doubler(). La seule différence, c'est que, au lieu de manipuler une référence EntierOuChaine& et de passer par la virtualité, on manipule un objet EntierOuChaineHandle et on utilise un std::variant.
    D'ailleurs, quand on compile du code qui utilise le type EntierOuChaineHandle, on n'est pas obligé de savoir quel est le type réel de la donnée (Entier et Chaine) à la compilation, ce qui fait un point commun avec l'héritage, même si, contrairement à l'héritage, le nombre de types réels est fini.

    Si on considérait Entier& et Chaine& comme des sous-types de EntierOuChaineHandle, alors on pourrait qualifier de polymorphisme par sous-typage le code de la fonction void quadrupler(EntierOuChaineHandle).
    Mais, avec les définitions actuelles, on qualifierait cela uniquement de polymorphisme de coercition, car on a des conversions implicites de Entier& et de Chaine& vers EntierOuChaineHandle, mais pas d'héritage.

    Mon opinion actuelle est que, avec la définition courante du polymorphisme par sous-typage (alias polymorphisme d'inclusion), l'expression la plus adéquate est polymorphisme par héritage. Comme ça, la frontière est très nette dans le nom lui-même. D'ailleurs, dans la page Polymorphisme du Wikipédia français, l'expression polymorphisme par héritage fait déjà partie des alias de polymorphisme par sous-typage.

  5. #5
    Expert éminent sénior
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    septembre 2005
    Messages
    26 473
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : septembre 2005
    Messages : 26 473
    Points : 38 201
    Points
    38 201

    Par défaut

    Citation Envoyé par Bktero Voir le message
    Est-ce vraiment un sous-type ? Le terme "sous-type" n'implique pas une notion d'héritage ?
    Ou, abordant la question sous un autre angle, Type& respecte-t-il tous les invariants de const Type&?
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  6. #6
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Ingénieur systèmes embarqués
    Inscrit en
    juin 2009
    Messages
    3 543
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 30
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Ingénieur systèmes embarqués

    Informations forums :
    Inscription : juin 2009
    Messages : 3 543
    Points : 9 335
    Points
    9 335
    Billets dans le blog
    1

    Par défaut

    Citation Envoyé par Pyramidev Voir le message
    C'est l'objet du fil
    C'est l'objet du fil ou c'est le postulat de base ? Moi, j'ai compris que c'était le postulat de base et qu'à partir de ce postulat, tu cherchais le "bon polymorphisme".

    Citation Envoyé par Médinoc Voir le message
    Ou, abordant la question sous un autre angle, Type& respecte-t-il tous les invariants de const Type&?
    Je m'étais justement posé la question en terme de respect du LSP. Et j'ai l'impression que ça fonctionne. C'est d'ailleurs ce que montre l'exemple de boulanger / pâtissier de Pyramidev. Néanmoins, je me demande quand même si on n'est pas en train de tomber dans le cas "ça ressemble à un canard, donc c'est un canard". En effet, d'un point de vue sémantique, le terme de sous-type implique SousType dérive de Type. Or, ce n'est pas Type qui dérive de const Type, c'est l'inverse.

    Après, c'est un débat C++esque : pas de ça en Java ou en Python, visiblement pas non plus en C#. C'est sans doute aussi pour ça que les définitions du polymorphisme par sous-typage ne parle pas de ça

  7. #7
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    Consultant informatique
    Inscrit en
    octobre 2004
    Messages
    10 534
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : Belgique

    Informations professionnelles :
    Activité : Consultant informatique

    Informations forums :
    Inscription : octobre 2004
    Messages : 10 534
    Points : 23 133
    Points
    23 133

    Par défaut

    Salut,

    En fait, le mot clé const (comme le mot clé volatile) font partie de la catégorie dite des cv-qualifiers. Come l'a indiqué Luc, ces mots clés ne modifient absolument en rien la représentation de la donnée en mémoire et ils ne créent pas d'avantage de nouveaux types que ne le fait la notion de référence.

    Car, bien que, dans les fait, une référence s'apparente (au niveau du code binaire généré à tout le moins) à un pointeur sur lequel C++ impose des restrictions, il en va de même des cv-qualifiers.

    En effet, C++ va appliquer la cohercition lorsqu'il rencontre un cv-qualifier ou le symbole représentant une référence (AKA l'esperluette), dans le sens oùet
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    void foo(Type const &){
    }
    ou
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    void foo(Type const){
    }
    ou encore
    sont bel et bien des surcharges (à ne pas confondre avec la redéfinition du polymorphisme d'inclusion ) d'une même fonction, et ce, malgré le fait que certaines de ces surcharges soient similaires.

    Il n'empêche cependant que, quelque soit le type de la donnée représenté par Type, tu l'utilisera toujours de la même manière dans foo.

    D'un point de vue conceptuel, nous sommes donc bel et bien dans un contexte de surcharge de fonction : tu peux parfaitement avoir au moins deux de ces signatures dans ton code car C++ pourra faire la différence, alors que le type que foo manipule reste exactement le même, modulo toutefois les éventuelles restrictions du au CV-qualifiers et à l'esperluette
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

Discussions similaires

  1. Réponses: 5
    Dernier message: 20/07/2013, 14h36
  2. Réponses: 13
    Dernier message: 03/09/2009, 21h40
  3. Réponses: 4
    Dernier message: 17/02/2009, 17h43
  4. probleme de "const correctness"
    Par GuiYom00 dans le forum C
    Réponses: 2
    Dernier message: 13/10/2008, 14h21
  5. problème avec const correctness
    Par donkeyquote dans le forum C++
    Réponses: 5
    Dernier message: 12/10/2007, 01h55

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo