Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Cast de const vers non-const

12 réponses
Avatar
diego-olivier.fernandez-pons
Bonjour,

Il y a une discussion sur fclc++ initi=E9e par Samuel Krempp (coucou
Samuel) au sujet de la n=E9cessit=E9 d'=E9crire certaines fonctions deux
fois, une fois en const et une fois non-const.

http://groups.google.com/group/fr.comp.lang.c++/browse_thread/thread/da5a05=
72c66e5ce8/8ed30a99cd556789

Ce que je ne comprends pas est pourquoi C++ ne peut pas caster
automatiquement une fonction const en non-const. Par exemple dans mon
cas particulier, je d=E9rive d'une classe abstraite qui demande une
fonction non-const.

class GraphInterface {
virtual int get_number_of_nodes () =3D 0;
}

et ma classe concr=E8te d=E9finit
int get_number_of_nodes () const { return number_of_nodes; }

Le compilateur (GCC) dit que les signatures ne correspondent pas (et du
coup que ma classe reste virtuelle) et tout ce corrige d=E8s que
j'enl=E8ve le const de l'impl=E9mentation. Mais je ne vois pas en quoi le
cast automatique pourrait compromettre la s=E9curit=E9 de l'application.

Diego Olivier

10 réponses

1 2
Avatar
Jean-Marc Bourguet
writes:

Bonjour,

Il y a une discussion sur fclc++ initiée par Samuel Krempp (coucou
Samuel) au sujet de la nécessité d'écrire certaines fonctions deux
fois, une fois en const et une fois non-const.

http://groups.google.com/group/fr.comp.lang.c++/browse_thread/thread/da5a0572c66e5ce8/8ed30a99cd556789

Ce que je ne comprends pas est pourquoi C++ ne peut pas caster
automatiquement une fonction const en non-const. Par exemple dans mon
cas particulier, je dérive d'une classe abstraite qui demande une
fonction non-const.

class GraphInterface {
virtual int get_number_of_nodes () = 0;
}

et ma classe concrète définit
int get_number_of_nodes () const { return number_of_nodes; }


Le problème ici, c'est GraphInterface qui ne fournit pas une interface
respectant const à implémenter. Evidemment, quand tu essaies de le faire
tu as des problèmes.

Le compilateur (GCC) dit que les signatures ne correspondent pas (et du
coup que ma classe reste virtuelle) et tout ce corrige dès que
j'enlève le const de l'implémentation. Mais je ne vois pas en quoi le
cast automatique pourrait compromettre la sécurité de l'application.


A mon avis, il y a déjà trop de possibilités de conversions implicites en
C++ pour qu'on en ajoute encore. Et ici, je ne vois pas quand tu veux la
conversion implicite, j'ai plutôt l'impression que tu veux une définition
de fonction implicite.

Dans le cas où la fonction est virtuelle pure, on peut le comprendre --
même si je n'aime pas trop ce "do what I mean", ça me semble passé de mode
dans la conception des langages depuis les années 70 --, mais si elle est
simplement virtuelle, est-ce que tu le voudrais aussi? Si oui, tu veux
donc imposer au gens de supplanter une fonction pour en refournir
immédiatement une implémentation équivalente à celle qui existe dans les
cas où la version de la classe parent est la bonne; si non, pourquoi la
différence entre le cas pure et le cas non pure?

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org

Avatar
Sylvain
wrote on 20/09/2006 19:16:

[...] Mais je ne vois pas en quoi le cast automatique
pourrait compromettre la sécurité de l'application.


la sécurité ? en rien !
(on pourrait même dire que le compilo ne se soucie pas de la sécurité de
l'application - c'est un concept vague - il applique simplement des règles).

au nombre de ces règles, vous vous devez d'implémenter les méthodes
virtuelles pures des classes de base pour pouvoir instantier votre
classe; et cela passe par le respect du prototype initial.

vous pouvez mettre des const partout ou nulle part, mais vous ne pouvez
pas alterner.

ce point n'est pas identique au cast implicite ('cast' est abusif) qui
permets l'appel d'une function const sur un objet non const.

Sylvain.

Avatar
diego-olivier.fernandez-pons
Bonjour,

Je réponds à Sylvain et à Jean-Marc à la fois.
Je vais essayer de décomposer le raisonnement en vertu duquel je suis
surpris que cela ne marche pas:

1. "const" c'est bien
- optimisations compilateur (peut être)
- typage plus fort, invariants garantis par le compilateur

2. Je déclare "const" une fonction qui me semble être logiquement
const à savoir graph.getNumberOfNodes () const, d'ailleurs la plupart
des bibliothèques de graphes le font.

3. J'implémente une interface fournie par un tiers (Graph) avec ma
propre structure de données, par exemple pour bénéficier de son
algorithme X, basé sur Graph avec mes optimisations personnelles de la
structure de données.

4. Le tiers en question a déclaré getNumberOfNodes non-const, sans
doute pour ne pas ajouter de contraintes trop importantes sur les
implémentations concrètes.

Raisonnable, encore que le cas des caches est couvert par 'mutable'.
[Je peux d'ailleurs changer le code "tiers" mais ce n'est pas la
question].

5. Je fournis getNumberOfNodes () const et le compilateur n'est pas
content !

C'est étrange, je suis obligé en quelque sorte d'oublier une bonne
propriété de mon implémentation (un invariant) pour pouvoir
implémenter l'interface. C'est en quelque sorte contraire au principe
"qui peut le plus, peut le moins" et au fait que les "const" quand ils
sont raisonnables sont une bonne pratique.

Diego Olivier
Avatar
Cyrille
C'est étrange, je suis obligé en quelque sorte d'oublier une bonne
propriété de mon implémentation (un invariant) pour pouvoir
implémenter l'interface. C'est en quelque sorte contraire au principe
"qui peut le plus, peut le moins" et au fait que les "const" quand ils
sont raisonnables sont une bonne pratique.


Ben oui, mais bon la faute en est au tiers qui a voulu que son interface
soit non const, je pense.
Sinon, pourquoi ne pas simplement implémenter une fonction non-const qui
appellerait une fonction const de votre choix? Dans votre
implémentation, vous aurez toujours l'invariant.

--
Les lois sont toujours utiles à ceux qui possèdent et nuisibles à ceux
qui n'ont rien. ~ Rousseau, du Contrat Social.

Avatar
Falk Tannhäuser
schrieb:
Il y a une discussion sur fclc++ initiée par Samuel Krempp (coucou
Samuel) au sujet de la nécessité d'écrire certaines fonctions deux
fois, une fois en const et une fois non-const.
http://groups.google.com/group/fr.comp.lang.c++/browse_thread/thread/da5a0572c66e5ce8/8ed30a99cd556789


26 Juillet 2001 - ça ne nous rajeunit pas...

Ce que je ne comprends pas est pourquoi C++ ne peut pas caster
automatiquement une fonction const en non-const. Par exemple dans mon
cas particulier, je dérive d'une classe abstraite qui demande une
fonction non-const.

class GraphInterface {
virtual int get_number_of_nodes () = 0;
}

et ma classe concrète définit
int get_number_of_nodes () const { return number_of_nodes; }

Le compilateur (GCC) dit que les signatures ne correspondent pas (et du
coup que ma classe reste virtuelle) et tout ce corrige dès que
j'enlève le const de l'implémentation. Mais je ne vois pas en quoi le
cast automatique pourrait compromettre la sécurité de l'application.


GCC a raison - le 'const' fait partie de la signature. Ceci n'est pas
forcement une question de sécurité - la raison de cette règle est plutôt
de permettre la surcharge des fonctions membres const et non-const.

Le problème me semble l'absence du const dans la classe de base. Si tu
ne peux pas corriger cela, tu es obligé de redéfinir la fonction comme
étant non-const dans la classe dérivée, ce qui ne t'empêche pas de la
surcharger par une version const non virtuelle, qui peut servir à
implémenter la version non-const :

classe Concrete : public GraphInterface
{
//...

int get_number_of_nodes() // Redéfinition de la fonction vituelle
{
return const_cast<Concrete const*>(this)->get_number_of_nodes();
// Ou, si tu n'aimes pas le const_cast,
// une conversion implicite fait l'affaire :
Concrete const* const_this = this;
return const_this->get_number_of_nodes();
}

int get_number_of_nodes() const { return number_of_nodes; }
};

Falk

Avatar
kanze
wrote:

Ce que je ne comprends pas est pourquoi C++ ne peut pas caster
automatiquement une fonction const en non-const. Par exemple
dans mon cas particulier, je dérive d'une classe abstraite qui
demande une fonction non-const.

class GraphInterface {
virtual int get_number_of_nodes () = 0;
}

et ma classe concrète définit
int get_number_of_nodes () const { return number_of_nodes; }

Le compilateur (GCC) dit que les signatures ne correspondent
pas (et du coup que ma classe reste virtuelle) et tout ce
corrige dès que j'enlève le const de l'implémentation. Mais je
ne vois pas en quoi le cast automatique pourrait compromettre
la sécurité de l'application.


Je ne crois pas ce que tu veux s'appelle une cast. Si j'ai bien
compris, tu veux que si tu fournis une fonction const avec la
même signature de une fonction virtuelle non-const dans la
classe de base, cette fonction supplanterait la fonction de la
classe de base. Je ne suis pas sûr que ce soit une bonne idée ;
suppose, par exemple, que la fonction n'était pas virtuelle pure
dans GraphInterface, que tu voulais utiliser l'implémentation
par défaut, mais que tu voulais aussi fournir une version à
fonctionnalité réduite, non prévue dans la classe de base, que
le client pourrait invoquer sur un objet const ?

Note que ce que tu démandes n'est qu'un cas particulier de la
contravariance ; une fonction membre a un paramètre implicit
qui devient le pointeur this, et le const de la fonction joue en
fait sur le type de ce paramètre. Le comité de normalisation a
discuté la possibilité d'ajouter la contravariance au C++ au
même temps qu'il a ajouté le covariance. Il a décidé de ne pas
le faire. Je n'ai pas participé dans les discusions, et je ne
connais pas les arguments qui ont été présenté pour ou contre,
mais dans l'ensemble, je suis assez sceptique, parce que je
crains des effets de bord dans la résolution du surcharge et
d'autres ambiguïtés.

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Avatar
kanze
Jean-Marc Bourguet wrote:
writes:


[...]
Ce que je ne comprends pas est pourquoi C++ ne peut pas caster
automatiquement une fonction const en non-const. Par exemple dans mon
cas particulier, je dérive d'une classe abstraite qui demande une
fonction non-const.

class GraphInterface {
virtual int get_number_of_nodes () = 0;
}

et ma classe concrète définit
int get_number_of_nodes () const { return number_of_nodes; }


Le problème ici, c'est GraphInterface qui ne fournit pas une interface
respectant const à implémenter.


C'est ce qu'on pourrait penser, vue le nom de la fonction. Mais
en général, rien ne me dit que l'interface veut imposer une
absence de mutation dans l'implémentation de la fonction. Si ce
n'est pas le cas, ce qu'il veut faire, c'est en fait utiliser de
la contravariance pour étendre l'interface. C'est exactement
pareil à s'il avait dans la classe de base une fonction :
void doSomething( Derived* withThis ) ;
et il voulait la supplanter par :
void doSomething( Base* withThis ) ;
(sauf, évidemment, le paramètre en question est le paramètre
implicit).

Le C++ supporte la covariance, mais non la contravariance.
Donc :
T const* Base::f() ;
peut être supplantée par :
T* Derived::f() ;
mais :
void Base::f( T* ) ;
ne peut pas être supplantée par :
void Derived::f( T const* ) ;

Evidemment, quand tu essaies de le faire
tu as des problèmes.


Dans son cas, la solution est simple :

virtual int get_number_of_nodes() {
return const_cast< Concrete const* >( this )
->get_number_of_nodes() ;
}
int get_number_of_nodes() const ;

(C'est d'ailleurs la solution générale quand on veut la
contravariance.)

Le comité a discuté la contravariance, au même temps qu'il a
considéré la covariance, et a décidé de ne pas le supporter.

Le compilateur (GCC) dit que les signatures ne correspondent pas (et du
coup que ma classe reste virtuelle) et tout ce corrige dès que
j'enlève le const de l'implémentation. Mais je ne vois pas en quoi le
cast automatique pourrait compromettre la sécurité de l'application.


A mon avis, il y a déjà trop de possibilités de conversions implici tes en
C++ pour qu'on en ajoute encore. Et ici, je ne vois pas quand tu veux la
conversion implicite, j'ai plutôt l'impression que tu veux une défini tion
de fonction implicite.


Je suis d'accord en ce qui concerne les conversions implicites,
mais comme tu as rémarqué, il a mal formulé ce qu'il voulait.
J'ai l'impression en fait que ce qu'il veut, c'est de la
contravariance.

Dans le cas où la fonction est virtuelle pure, on peut le comprendre --
même si je n'aime pas trop ce "do what I mean", ça me semble passé de mode
dans la conception des langages depuis les années 70 --, mais si elle e st
simplement virtuelle, est-ce que tu le voudrais aussi? Si oui, tu veux
donc imposer au gens de supplanter une fonction pour en refournir
immédiatement une implémentation équivalente à celle qui existe d ans les
cas où la version de la classe parent est la bonne; si non, pourquoi la
différence entre le cas pure et le cas non pure?


C'était peut-être un raisonement comme ça qui a mené au rejet de
la contravariance. (Mais je ne sais pas au juste. Je n'y ai pas
participé.)

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Avatar
Yoxoman
kanze :

Le C++ supporte la covariance, mais non la contravariance.
Donc :
T const* Base::f() ;
peut être supplantée par :
T* Derived::f() ;
mais :
void Base::f( T* ) ;
ne peut pas être supplantée par :
void Derived::f( T const* ) ;


Une question sur la covariance :

apparemment,

double Base::f()

ne peut être supplantée par

int Derived::f()

Pourquoi ? N'a-t-on pas int <= double ?

Avatar
kanze
Yoxoman wrote:
kanze :

Le C++ supporte la covariance, mais non la contravariance.
Donc :
T const* Base::f() ;
peut être supplantée par :
T* Derived::f() ;
mais :
void Base::f( T* ) ;
ne peut pas être supplantée par :
void Derived::f( T const* ) ;


Une question sur la covariance :

apparemment,

double Base::f()

ne peut être supplantée par

int Derived::f()

Pourquoi ? N'a-t-on pas int <= double ?


Ça veut dire quoi, <= ici ?

Pour que la covariance soit légale, il faut qu'il y a un rapport
sur le système des types ; double et int sont bien deux types
distincts et non-apparentés. Le fait qu'il y a une conversion
possible ne rend pas possible la covariance. (En fait, en C++,
la covariance ne fonctionne qu'avec des références ou des
pointeurs. Et les conversions permises sont, grosso modo, celui
de Base* vers Derived* et de T cv1* vers T cv2*, où cv2 est un
sous-ensemble de cv1.)

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Avatar
Yoxoman
kanze :

Pourquoi ? N'a-t-on pas int <= double ?


Ça veut dire quoi, <= ici ?


Inferieur ou égal.

Ca vient de

http://en.wikipedia.org/wiki/Parameter_covariance

En fait, comme ils ne définissent pas cette relation d'ordre pour les
types, j'ai extrapolé que T <= T' si tout objet de type T peut être
converti implicitement vers T' (polymorphisme ad hoc). C'est bien le cas
de Derived* vers Base* et T* vers const T*.

De plus, l'exemple de wikipedia sur Java

http://en.wikipedia.org/wiki/Parameter_covariance#Java

traitant de int[] et double[] laisse entendre qu'on a bien int <= double
(du moins en Java).

Pour que la covariance soit légale, il faut qu'il y a un rapport
sur le système des types ; double et int sont bien deux types
distincts et non-apparentés. Le fait qu'il y a une conversion
possible ne rend pas possible la covariance. (En fait, en C++,
la covariance ne fonctionne qu'avec des références ou des
pointeurs. Et les conversions permises sont, grosso modo, celui
de Base* vers Derived* et de T cv1* vers T cv2*, où cv2 est un
sous-ensemble de cv1.)


Merci.

Aurais-tu des références (même dans la norme, soyons fous) pour la
partie non-grosso modo ?


1 2