OVH Cloud OVH Cloud

Pourquoi l'operateur -> const des smart ptr renvoie un non const ?

28 réponses
Avatar
Aurelien Regat-Barrel
Bonjour,
Je suis surpris et déçu de constater que plusieurs pointeurs
intelligents (auto_ptr, boost) ont leur opérateur d'indirection défini
ainsi:

T* operator->() const;

alors que je me serais attendu à:

T* operator->();
const T* operator->() const;

ce qui permet d'avoir une erreur de compilation dans l'exemple qui suit:

class A
{
public:
void f1() const {}
void f2() {}
};

const auto_ptr<A> p( new A ); // <- const
p->f1();
p->f2(); // erreur de compilation attendue mais...

Du coup il faut passer par un

auto_ptr<const A>

qui ne peut pas être implicitement converti depuis un

auto_ptr<A>

:/
(cela dit ça marche avec boost, dans une certaine mesure). Je supose
qu'il y a une bonne raison à celà, mais je ne la devine pas. La
connaissez-vous ?

Merci.

--
Aurélien Regat-Barrel

8 réponses

1 2 3
Avatar
Aurelien Regat-Barrel
Aurelien Regat-Barrel wrote:

Ok. Avec un autre const, ça marche:

char * a = 0;
char const * const & b = a;

Du coup, je m'attends à pouvoir écrire:

std::vector<char> a;
std::vector<char> const & b = a; // OK
std::vector<char const> const & c = a; // :-(



std::vector<char const> ne passe pas parce que
le type qu'on met dans un vector doit supporter
l'affectation.


Je vais arrêter les exemples fouarreux ici...

En revanche, on peut imaginer des conversions qui seraient parfaitement
sûres mais qui ne sont néanmoins pas supportées par le langage (sans doute
personne ne les a pas proposées pour la standardisation parce que
les cas où elles seraient utiles se présentent trop rarement dans
la pratique). Par exemple
std::vector<char*>::iterator ==> std::vector<char
const*>::const_iterator
std::vector<char>::iterator* ==> std::vector<char>::const_iterator
const*
(Note: Ces deux conversions passeront la compilation sur les
implémentations
où 'std::vector<T>::[[const_]]iterator' est défini comme 'T [[const]]*'.
Ces
implémentations semblent toutefois devenir de plus en plus rares ces
jours-ci.)

Similairement, il pourrait aussi y avoir des conversions entre pointeurs
sur les fonctions comme
char* (*)() ===> char const* (*)()
void (*)(char const*) ===> void (*)(char*)


C'est un peu ce que j'esperais avoir.
Merci.

--
Aurélien Regat-Barrel


Avatar
Aurelien Regat-Barrel
Aurelien Regat-Barrel wrote:

[...]
Après réflexion, je réalise que c'est au niveau des références
qu'est le problème. Ceci compile:

char a;
char const & b = a;

mais pas ceci:

char * a = 0;
char const * & b = a;

En fait, je ne comprends pas pourquoi.


Parce que :

char * a = 0 ;
char const* & b = a ;
char const c = 'x' ;
b = &c ;

Et où est-ce que « a » pointe maintenant ?

A peu de chose près, j'imagine que c'est la même règle qui
s'applique pour le refus d'initialiser une référence sur un
template<T const> à partir d'un template<T>.


Pas tout à fait. T et T const sont deux types distincts.
Apparenté, bien sûr, mais quand même des types différents. Ce
qui fait qu'ils donnent lieu à des instances de template
distinctes. Seulement, il n'y a pas d'apparenté entre les
différentes instances d'un template, même s'il y en a entre
leurs paramètres.

Dans la pratique, je ne vois pas comment ça pourrait ne pas être
le cas. C'est facile de trouver des cas particuliers où ça
serait commode si, mais essaie de formuler une règle générale et
absolue qui s'appliquerait à tous les templates.


J'en suis incapable, mais j'ai tendance à penser que c'est faisable. Je
m'initie doucement à C++/CLI et j'ai découvert le principe d'"array
covariance":

array<int> ^a = gcnew array<int>{ 1, 2, 3 };
array<const int> ^b = a;
a[ 0 ] = 4;
Console::WriteLine( b[ 0 ] ); // affiche 4
b[ 0 ] = 5; // erreur
array<int> ^c = b; // erreur

c'est pas vraiment comparable (array n'est pas un template), mais je
trouve cette possibilité plutôt sympa.

Du coup, j'ai une copie profonde que je n'ai peut-être pas
voulue. Ça coûte cher, copier tout un vecteur.

Note bien que si tu veux la copie, tu peux toujours écrire :

Test2( AConstVect( v.begin(), v.end() ) ) ;

C'est la façon à appliquer la conversion explicite sur chaque
élément du vecteur.


Je pense que je vais opter pour cette solution, avec un peu de
chance le compilateur fera une bonne optimisation.


Disons que c'est l'inverse. Je ne sais pas à quelle optimisation
tu t'attends, mais déjà avec la génération d'un temporaire, tu
as (ou aurais) une copie profonde. Cette forme ne serait pas
pire.


L'optimisation que j'attends, c'est que le compilateur se rende compte
que la construction du AConstVect temporaire est inutile et qu'il peut
directement utiliser v...

--
Aurélien Regat-Barrel



Avatar
kanze
Aurelien Regat-Barrel wrote:

[...]
Pas tout à fait. T et T const sont deux types distincts.
Apparenté, bien sûr, mais quand même des types différents.
Ce qui fait qu'ils donnent lieu à des instances de template
distinctes. Seulement, il n'y a pas d'apparenté entre les
différentes instances d'un template, même s'il y en a entre
leurs paramètres.

Dans la pratique, je ne vois pas comment ça pourrait ne pas
être le cas. C'est facile de trouver des cas particuliers où
ça serait commode si, mais essaie de formuler une règle
générale et absolue qui s'appliquerait à tous les templates.


J'en suis incapable, mais j'ai tendance à penser que c'est
faisable. Je m'initie doucement à C++/CLI et j'ai découvert le
principe d'"array covariance":

array<int> ^a = gcnew array<int>{ 1, 2, 3 };
array<const int> ^b = a;
a[ 0 ] = 4;
Console::WriteLine( b[ 0 ] ); // affiche 4
b[ 0 ] = 5; // erreur
array<int> ^c = b; // erreur

c'est pas vraiment comparable (array n'est pas un template),
mais je trouve cette possibilité plutôt sympa.


C'est une chose d'introduire la covariance dans certaines
structures bien précises ; c'est une autre d'en faire une
principe générale. Est-ce qu'un array<Derived> pourrait servir à
la place d'un array<Base>, par exemple ?

Il y a une technique dans la bibliothèque standard qui permet de
faire ce genre de chose : les constructeurs templatés à deux
itérateurs. Dans le cas de la bibliothèque standard, il faut en
être explicite, mais je ne suis pas sûr que c'est un
désavantage ; je n'aime pas trop des conversions implicites dans
tous les sens.

Du coup, j'ai une copie profonde que je n'ai peut-être
pas voulue. Ça coûte cher, copier tout un vecteur.

Note bien que si tu veux la copie, tu peux toujours
écrire :

Test2( AConstVect( v.begin(), v.end() ) ) ;

C'est la façon à appliquer la conversion explicite sur
chaque élément du vecteur.


Je pense que je vais opter pour cette solution, avec un
peu de chance le compilateur fera une bonne optimisation.


Disons que c'est l'inverse. Je ne sais pas à quelle
optimisation tu t'attends, mais déjà avec la génération d'un
temporaire, tu as (ou aurais) une copie profonde. Cette
forme ne serait pas pire.


L'optimisation que j'attends, c'est que le compilateur se
rende compte que la construction du AConstVect temporaire est
inutile et qu'il peut directement utiliser v...


C'est beaucoup démander. Il s'agit ici des types définis par
l'utilisateur, avec des constructeurs non-triviaux. Pour qu'un
compilateur puisse déduire que le comportement visible du
programme ne change pas en supprimant la copie, ça ne me semble
pas du tout trivial.

--
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
Aurelien Regat-Barrel

J'en suis incapable, mais j'ai tendance à penser que c'est
faisable. Je m'initie doucement à C++/CLI et j'ai découvert le
principe d'"array covariance":



array<int> ^a = gcnew array<int>{ 1, 2, 3 };
array<const int> ^b = a;
a[ 0 ] = 4;
Console::WriteLine( b[ 0 ] ); // affiche 4
b[ 0 ] = 5; // erreur
array<int> ^c = b; // erreur



c'est pas vraiment comparable (array n'est pas un template),
mais je trouve cette possibilité plutôt sympa.



C'est une chose d'introduire la covariance dans certaines
structures bien précises ; c'est une autre d'en faire une
principe générale. Est-ce qu'un array<Derived> pourrait servir à
la place d'un array<Base>, par exemple ?


Oui.
http://msdn2.microsoft.com/en-US/library/y8ez8w64(VS.80).aspx

Il y a une technique dans la bibliothèque standard qui permet de
faire ce genre de chose : les constructeurs templatés à deux
itérateurs. Dans le cas de la bibliothèque standard, il faut en
être explicite, mais je ne suis pas sûr que c'est un
désavantage ; je n'aime pas trop des conversions implicites dans
tous les sens.


Pour moi, il s'agit de généraliser des conversions implicites déjà
existantes (non const -> const, derived -> base, ...).

A+

--
Aurélien Regat-Barrel


Avatar
James Kanze
Aurelien Regat-Barrel wrote:

J'en suis incapable, mais j'ai tendance à penser que c'est
faisable. Je m'initie doucement à C++/CLI et j'ai découvert
le principe d'"array covariance":




array<int> ^a = gcnew array<int>{ 1, 2, 3 };
array<const int> ^b = a;
a[ 0 ] = 4;
Console::WriteLine( b[ 0 ] ); // affiche 4
b[ 0 ] = 5; // erreur
array<int> ^c = b; // erreur




c'est pas vraiment comparable (array n'est pas un template),
mais je trouve cette possibilité plutôt sympa.




C'est une chose d'introduire la covariance dans certaines
structures bien précises ; c'est une autre d'en faire une
principe générale. Est-ce qu'un array<Derived> pourrait
servir à la place d'un array<Base>, par exemple ?



Oui.
http://msdn2.microsoft.com/en-US/library/y8ez8w64(VS.80).aspx


Ça poserait pas mal de problème en C++. Quelle est la taille
d'un élément. (En C#, si je ne me trompe pas, c'est comme en
Java : les éléments sont tous des pointeurs.)

Note aussi que ce n'est pas sans danger ; en Java, au moins,
c'est un des rares cas où on peut violer le système de typage :

void f( Base[] b )
{
b[ 0 ] = new Base() ;
}

// ...
Derived d[] = new Derived[ 10 ] ;
f( d ) ;
// Et le type de d[0] est...

Il y a une technique dans la bibliothèque standard qui permet
de faire ce genre de chose : les constructeurs templatés à
deux itérateurs. Dans le cas de la bibliothèque standard, il
faut en être explicite, mais je ne suis pas sûr que c'est un
désavantage ; je n'aime pas trop des conversions implicites
dans tous les sens.



Pour moi, il s'agit de généraliser des conversions implicites
déjà existantes (non const -> const, derived -> base, ...).


Je comprends bien -- à condition de le limiter à ces deux
types de conversions, et que pour les pointeurs et les
références dans le dernier cas. En revanche, je ne crois pas que
je saurais la définir.

Ce qui pourrait être utile, c'est une façade pour les
collections standard -- une classe qui présente l'interface
d'une collection, mais renvoie à un autre objet pour les données
réeles. Du coup, toutes les fonctions d'accès (y compris celles
des itérateurs) s'occuperait des conversions. En plus, elles ne
pourraient être instanciées que si la conversion implicite était
légale. Donc, si j'ai une Fassade< Base*, std::vector< Derived*
, toutes les fonctions de lecture marchent, parce qu'elles
effectue une conversion Derived* en Base* ; les fonctions


d'insertion, en revanche, non, parce qu'elles essaient
d'effectuer une conversion implicite Base* vers Derived*.

Ce que je suggère, si tu le crois important, c'est de
l'implémenter et de le proposer à Boost. Ensuite, il y a des
chances qu'il fasse partie de C++1x.

--
James Kanze
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
Aurelien Regat-Barrel

C'est une chose d'introduire la covariance dans certaines
structures bien précises ; c'est une autre d'en faire une
principe générale. Est-ce qu'un array<Derived> pourrait
servir à la place d'un array<Base>, par exemple ?


Oui.
http://msdn2.microsoft.com/en-US/library/y8ez8w64(VS.80).aspx


Ça poserait pas mal de problème en C++. Quelle est la taille
d'un élément. (En C#, si je ne me trompe pas, c'est comme en
Java : les éléments sont tous des pointeurs.)

Note aussi que ce n'est pas sans danger ; en Java, au moins,
c'est un des rares cas où on peut violer le système de typage :

void f( Base[] b )
{
b[ 0 ] = new Base() ;
}

// ...
Derived d[] = new Derived[ 10 ] ;
f( d ) ;
// Et le type de d[0] est...


Pour info, en C++/CLI, cet exemple (adapté) lève une exception
ArrayTypeMismatchException.

Il y a une technique dans la bibliothèque standard qui permet
de faire ce genre de chose : les constructeurs templatés à
deux itérateurs. Dans le cas de la bibliothèque standard, il
faut en être explicite, mais je ne suis pas sûr que c'est un
désavantage ; je n'aime pas trop des conversions implicites
dans tous les sens.


Pour moi, il s'agit de généraliser des conversions implicites
déjà existantes (non const -> const, derived -> base, ...).


Je comprends bien -- à condition de le limiter à ces deux
types de conversions, et que pour les pointeurs et les
références dans le dernier cas. En revanche, je ne crois pas que
je saurais la définir.

Ce qui pourrait être utile, c'est une façade pour les
collections standard -- une classe qui présente l'interface
d'une collection, mais renvoie à un autre objet pour les données
réeles. Du coup, toutes les fonctions d'accès (y compris celles
des itérateurs) s'occuperait des conversions. En plus, elles ne
pourraient être instanciées que si la conversion implicite était
légale. Donc, si j'ai une Fassade< Base*, std::vector< Derived*
, toutes les fonctions de lecture marchent, parce qu'elles
effectue une conversion Derived* en Base* ; les fonctions


d'insertion, en revanche, non, parce qu'elles essaient
d'effectuer une conversion implicite Base* vers Derived*.

Ce que je suggère, si tu le crois important, c'est de
l'implémenter et de le proposer à Boost. Ensuite, il y a des
chances qu'il fasse partie de C++1x.


Je vais réfléchir :-)

--
Aurélien Regat-Barrel



Avatar
kanze
Aurelien Regat-Barrel wrote:

void f( Base[] b )
{
b[ 0 ] = new Base() ;
}

// ...
Derived d[] = new Derived[ 10 ] ;
f( d ) ;
// Et le type de d[0] est...


Pour info, en C++/CLI, cet exemple (adapté) lève une exception
ArrayTypeMismatchException.


Java aussi, je crois. Mais le fait reste que tu as suivi les
règles, et que tu as violé le système du typage quand même.

--
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
Sylvain
kanze wrote on 11/05/2006 19:13:

void f( Base[] b ){
b[ 0 ] = new Base() ;
}
Derived d[] = new Derived[ 10 ] ;
f( d ) ;


Pour info, en C++/CLI, cet exemple (adapté) lève une exception
ArrayTypeMismatchException.


Java aussi, je crois. Mais le fait reste que tu as suivi les
règles, et que tu as violé le système du typage quand même.


Java aussi mais pas tout à fait (il lève une ArrayStoreException) ;)

Sylvain.



1 2 3