Conversion automatique et comparaison.

Le
Michel Decima
Bonjour,

J'ai un soucis de comprehension sur une classe qui possede
un operateur de conversion automatique (j'aurais du me mefier,
on m'avais prevenu, mais je l'ai fait quand meme)

Donc, soit la classe en question, reduite a sa plus
simple expression :

template< typename T >
class Wrapper
{
public:
explicit Wrapper( T const& value = T() ) : myValue( value ) {}
operator T () const { return myValue ; }
private:
T myValue ;
} ;

Dans le code, on avait ceci :

Wrapper< int > x ;
if ( x == 0 ) {
//
}

Pour le test d'egalite, il y a conversion automatique
vers int puis appel de l'operateur == sur int, ca compile,
ca marche, tout va bien.

Plus tard, on a changé le code, et remplace int par std::string.
Et la, c'est le drame : le compilateur refuse le test d'egalite,
comme s'il ne trouvait pas l'operateur de conversion automatique :

Wrapper< std::string > x ;
if ( x == std::string( "" ) ) {
//
}

erreur : no match for 'operator==' in 'x == y'

(testé avec gcc-4.1.2, xlC-8, Comeau 4.3.10.1).

Pour essayer de comprendre, j'ai remplacé std::string par une
classe vide, et la ca compile :

struct Empty {} ;
bool operator==( Empty const&, Empty const& ) { return true ; }

Wrapper< Empty > x ;
if ( x == Empty() ) ; // OK

Par contre, si la classe encapsulee est template, alors
ca ne compile plus :

template < typename T >
struct Generic {} ;
template < typename T >
bool operator==( Generic< T > const&, Generic< T > const& )
{ return true ; }

Wrapper< Generic< int > > x ;
if ( x == Generic< int>() ) ; // KO : no match for 'operator=='

Donc, il semblerait que dans une expression d'egalité, l'operateur
de conversion automatique de Wrapper< T > n'est pas trouvé lorsque
T est lui-meme un type template.
Est-ce qu'il y a une raison qui explique ce comportement ?

Merci.
Vidéos High-Tech et Jeu Vidéo
Téléchargements
Vos réponses
Gagnez chaque mois un abonnement Premium avec GNT : Inscrivez-vous !
Trier par : date / pertinence
Sylvain SF
Le #19428741
Michel Decima a écrit :
Bonjour,

J'ai un soucis de comprehension sur une classe qui possede
un operateur de conversion automatique (j'aurais du me mefier,
on m'avais prevenu, mais je l'ai fait quand meme...)



mais non, c'est utile.

Donc, soit la classe en question, reduite a sa plus
simple expression :

template< typename T >
class Wrapper {
public:
explicit Wrapper( T const& value = T() ) : myValue( value ) {}
operator T () const { return myValue ; }
private:
T myValue ;
} ;

Plus tard, on a changé le code, et remplace int par std::string.
Et la, c'est le drame : le compilateur refuse le test d'egalite,
comme s'il ne trouvait pas l'operateur de conversion automatique



j'aurais mis un operator (paramétré) de test dans le template:

bool operator ==(T const& t) const {
return myValue == t;
}

ça coûte pas grand chose et corrige le pb.

Sylvain.
Fabien LE LEZ
Le #19431481
On Wed, 27 May 2009 18:24:25 +0200, Michel Decima :

template < typename T >
struct Generic {} ;



template < typename T >
bool operator==( Generic< T > const&, Generic< T > const& )
{ return true ; }
Wrapper< Generic< int > > x ;
if ( x == Generic< int>() ) ; // KO : no match for 'operator=='



Simplifions un peu :

template < typename T > struct Generic {} ;

template < typename T >
void f (Generic< T > const&) {}

Wrapper< Generic< int > > x ;
f (x);

f<>() est une fonction template.
Le compilateur peut trouver (inférer) lui-même le type paramètre
template, à condition qu'aucune conversion implicite ne soit
nécessaire.
En d'autres termes, il faudrait qu'il existe un type T pour lequel x
soit de type Generic<T>. Ce n'est pas possible, donc le compilo ne
sait pas trouver T tout seul.

En revanche, tu peux indiquer le type explicitement :

Wrapper< Generic< int > > x ;
f<int> (x);

f<int>() est une fonction normale, qui accepte comme paramètre un
"Generic<int> const&", et x est convertible en ce type, donc tout va
bien.

Évidemment, avec operator== et basic_string<plein_de_trucs>,
expliciter les paramètres templates s'avère plus délicat :-(
James Kanze
Le #19432131
On May 27, 6:24 pm, Michel Decima
J'ai un soucis de comprehension sur une classe qui possede un
operateur de conversion automatique (j'aurais du me mefier, on
m'avais prevenu, mais je l'ai fait quand meme...)



Donc, soit la classe en question, reduite a sa plus
simple expression :



template< typename T >
class Wrapper
{
public:
explicit Wrapper( T const& value = T() ) : myValue( value ) {}
operator T () const { return myValue ; }
private:
T myValue ;
} ;



Dans le code, on avait ceci :



Wrapper< int > x ;
if ( x == 0 ) {
// ...
}



Pour le test d'egalite, il y a conversion automatique vers int
puis appel de l'operateur == sur int, ca compile, ca marche,
tout va bien.



Plus tard, on a changé le code, et remplace int par
std::string. Et la, c'est le drame : le compilateur refuse le
test d'egalite, comme s'il ne trouvait pas l'operateur de
conversion automatique :



Wrapper< std::string > x ;
if ( x == std::string( "" ) ) {
// ...
}



erreur : no match for 'operator==' in 'x == y'



Ce n'est pas la conversion implicite qu'il ne trouve pas. C'est
opérateur == tout court. Les seuls opérateurs == dont il peut
être question, ce sont ceux dans <string>. Sauf qu'ils ne sont
pas des fonctions, mais des templates, et ne peuvent être pris
en compte qu'une fois instantiées. Et la déduction automatique
des types, qui permettrait l'instantiation, ne prend pas en
compte les conversions implicites (ou seulement quelques
conversions très simples, du genre tableau en pointeur).

(testé avec gcc-4.1.2, xlC-8, Comeau 4.3.10.1).



Pour essayer de comprendre, j'ai remplacé std::string par une
classe vide, et la ca compile :



struct Empty {} ;
bool operator==( Empty const&, Empty const& ) { return true ; }



Vue que l'opérateur n'est pas un template.

Wrapper< Empty > x ;
if ( x == Empty() ) ; // OK



Par contre, si la classe encapsulee est template, alors
ca ne compile plus :



template < typename T >
struct Generic {} ;
template < typename T >
bool operator==( Generic< T > const&, Generic< T > const& )
{ return true ; }



Wrapper< Generic< int > > x ;
if ( x == Generic< int>() ) ; // KO : no match for 'operator= ='



Donc, il semblerait que dans une expression d'egalité,
l'operateur de conversion automatique de Wrapper< T > n'est
pas trouvé lorsque T est lui-meme un type template.



La différence, ce n'est pas T, c'est l'operator==. Si
l'operator== est une fonction templatée, la déduction de type,
ne prenant pas en compte les conversions implicites, échouera,
et il n'y aura pas de fonction à ajouter à l'ensemble de
surcharge.

La solution évidente, c'est de ne jamais faire operator== un
template. Faute d'autres solutions :

template< typename T >
class Generic
{
public:
friend bool operator==( Generic const& lhs, Generic const&
rhs )
{
return true /* ou d'autre chose */ ;
}
} ;

(Du coup, l'operator== n'est pas un template. Mais il y en a
autant qu'il faut.)

Pour std::string, évidement, il n'y a rien que tu puisses faire.
(Une question intéressante : est-ce qu'une implémentation de
std::basic_string a le droit de faire comme ci-dessus ?)

--
James Kanze (GABI Software) email:
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
Michel Decima
Le #19432211
Fabien LE LEZ a écrit :
On Wed, 27 May 2009 18:24:25 +0200, Michel Decima :



Simplifions un peu :

template < typename T > struct Generic {} ;

template < typename T >
void f (Generic< T > const&) {}

Wrapper< Generic< int > > x ;
f (x);

f<>() est une fonction template.
Le compilateur peut trouver (inférer) lui-même le type paramètre
template, à condition qu'aucune conversion implicite ne soit
nécessaire.



Ok, c'est l'explication que je cherchais : les conversions
implicites ne sont pas prises en compte dans l'inference
des parametres templates.

En revanche, tu peux indiquer le type explicitement :

Wrapper< Generic< int > > x ;
f<int> (x);

f<int>() est une fonction normale, qui accepte comme paramètre un
"Generic<int> const&", et x est convertible en ce type, donc tout va
bien.



Oui, mais j'aurais voulu eviter d'indiquer explicitement le type...


Évidemment, avec operator== et basic_string<plein_de_trucs>,
expliciter les paramètres templates s'avère plus délicat :-(



Ben voila... c'est tout le probleme.
Merci pour l'explication.
Michel Decima
Le #19432201
Sylvain SF a écrit :
Michel Decima a écrit :
Bonjour,

J'ai un soucis de comprehension sur une classe qui possede
un operateur de conversion automatique (j'aurais du me mefier,
on m'avais prevenu, mais je l'ai fait quand meme...)



mais non, c'est utile.



Je ne nie pas l'utilité. Mais il y a quand meme pas mal
d'inconvénients, certains sont bien décrits dans la
litterature, et je n'avais encore jamais lu quoi que
ce soit sur celui que je viens de trouver (ou alors,
je n'avait pas fait le rapprochement). Bref, c'est
casse pied.


j'aurais mis un operator (paramétré) de test dans le template:

bool operator ==(T const& t) const {
return myValue == t;
}

ça coûte pas grand chose et corrige le pb.



Oui, c'est surement comme ca que ca va finir, mais
je voulais avoir une explication sur le 'pourquoi'
de l'erreur produite par le compilateur.
Michel Decima
Le #19432451
James Kanze a écrit :
On May 27, 6:24 pm, Michel Decima
erreur : no match for 'operator==' in 'x == y'



Ce n'est pas la conversion implicite qu'il ne trouve pas. C'est
opérateur == tout court. Les seuls opérateurs == dont il peut
être question, ce sont ceux dans <string>. Sauf qu'ils ne sont
pas des fonctions, mais des templates, et ne peuvent être pris
en compte qu'une fois instantiées. Et la déduction automatique
des types, qui permettrait l'instantiation, ne prend pas en
compte les conversions implicites (ou seulement quelques
conversions très simples, du genre tableau en pointeur).



Ok, merci a toi et a Fabien pour l'explication detaillee.

Par contre, si la classe encapsulee est template, alors
ca ne compile plus :



template < typename T >
struct Generic {} ;
template < typename T >
bool operator==( Generic< T > const&, Generic< T > const& )
{ return true ; }



Wrapper< Generic< int > > x ;
if ( x == Generic< int>() ) ; // KO : no match for 'operator=='



Donc, il semblerait que dans une expression d'egalité,
l'operateur de conversion automatique de Wrapper< T > n'est
pas trouvé lorsque T est lui-meme un type template.



La différence, ce n'est pas T, c'est l'operator==. Si
l'operator== est une fonction templatée, la déduction de type,
ne prenant pas en compte les conversions implicites, échouera,
et il n'y aura pas de fonction à ajouter à l'ensemble de
surcharge.



J'ai ajouté la classe template Generic<> pour essayer de
comprendre d'ou venait le probleme, parce que je savais
que std::string etait elle meme template. Mais comme tu
le fais remarquer, c'etait une fausse piste, le probleme
n'est pas dans le type T, mais dans la fonction appelée
(l'exemple de Fabien avec la fonction f<>() le montre bien).

La solution évidente, c'est de ne jamais faire operator== un
template. Faute d'autres solutions :

template< typename T >
class Generic
{
public:
friend bool operator==( Generic const& lhs, Generic const&
rhs )
{
return true /* ou d'autre chose */ ;
}
} ;

(Du coup, l'operator== n'est pas un template. Mais il y en a
autant qu'il faut.)



Oui, ca marche, mais c'est quand meme subtil: on utilise
le friend non pas pour donner l'accès aux membres proteges/prives
de Generic, mais pour rendre operator== non template...
Bref, c'est pas evident a la premiere lecture.

Pour std::string, évidement, il n'y a rien que tu puisses faire.



Et c'est bien mon probleme...

(Une question intéressante : est-ce qu'une implémentation de
std::basic_string a le droit de faire comme ci-dessus ?)



Je ne sais pas.
James Kanze
Le #19439901
On May 28, 10:34 am, Michel Decima wrote:
James Kanze a écrit :


[...]
> La solution évidente, c'est de ne jamais faire operator== un
> template. Faute d'autres solutions :

> template< typename T >
> class Generic
> {
> public:
> friend bool operator==( Generic const& lhs, Generic const&
> rhs )
> {
> return true /* ou d'autre chose */ ;
> }
> } ;



> (Du coup, l'operator== n'est pas un template. Mais il y en a
> autant qu'il faut.)



Oui, ca marche, mais c'est quand meme subtil: on utilise le
friend non pas pour donner l'accès aux membres proteges/prives
de Generic, mais pour rendre operator== non template... Bref,
c'est pas evident a la premiere lecture.



En effet. Mais c'est un idiome plus ou moins répandu (due à
Barton et Nackman).

Dans la pratique, j'ai quelque classes templatées de base qui le
font :

template< typename T >
class Generic : public ComparisonOperators< Generic >
{
// ...
bool isEqual( Generic const& other ) const ;
} ;

ComparisonOperators génère un operator== et un operator!= à
partir de isEqual (et des opérateurs <, <=, > et >= à partir de
compare, si cette fonction existe) ; ArithmeticOperators fait de
même pour tous les opérateurs arithmétique, à partir des <op>=  ;
etc. Et la documentation de ces templates explique ce qui se
passe.

> Pour std::string, évidement, il n'y a rien que tu puisses
> faire.



Et c'est bien mon probleme...



Si il n'y a que std::string, tu peux fournir l'instance de
l'operator== toi-même. Le problème devient plus ardu si tu as
besoin de tous les basic_string possibles, de tous les vector,
etc.

> (Une question intéressante : est-ce qu'une implémentation de
> std::basic_string a le droit de faire comme ci-dessus ?)



Je ne sais pas.



Moi non plus. Mais je crains que non. La norme décrit la
fonction comme un template, déclarée en dehors de la classe (et
donc visible dans le namespace sans ADL). Et c'est bien trop
facile à imaginer des cas où un programme verrait une
différence, même si je doute que de tels cas se présentent dans
du code réel.

--
James Kanze (GABI Software) email:
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
Publicité
Poster une réponse
Anonyme