Lorsque je mets MonType2 à la place de MyType, aucun de ces 3 casts ne compile chez moi avec gcc 3.4.1 (dans un fonction non-membre - en l'occurrence main()), tout comme il se doit.
Oui je me suis trompé en copiant-collant, c'est MonType2:
class A { private: typedef int MonType1; // types privés class MonType2 {};
int main() { A a; a.Test1( 0 ); // OK a.Test2( 0 ); // OK a.Test1( static_cast<A::MonType1>( 0 ) ); // `class A::MonType1' is private a.Test2( static_cast<A::MonType2*>( 0 ) ); // `class A::MonType2' is private }
effectivement j'avais pas testé avec gcc, ça cause une erreur. C'est aussi le vas avec VC++ 8 ("cannot access private class declared in class 'A'"), mais VC++ 7.1 laisse passer. Mais je comprends toujours pas pourquoi le 0 passe. Pour moi, les 2 lignes étaitent équivalentes (0 avec et sans cast), car il y avait cast implicite. Je pensais que le compilo considérait 0 comme un MonType1 / MonType2* initialisé à 0, mais apparement non. Comment ça marche ? Où a lieu le cast implicite ?
-- Aurélien REGAT-BARREL
Lorsque je mets MonType2 à la place de MyType, aucun de ces 3 casts
ne compile chez moi avec gcc 3.4.1 (dans un fonction non-membre -
en l'occurrence main()), tout comme il se doit.
Oui je me suis trompé en copiant-collant, c'est MonType2:
class A
{
private:
typedef int MonType1; // types privés
class MonType2 {};
int main()
{
A a;
a.Test1( 0 ); // OK
a.Test2( 0 ); // OK
a.Test1( static_cast<A::MonType1>( 0 ) ); // `class A::MonType1' is
private
a.Test2( static_cast<A::MonType2*>( 0 ) ); // `class A::MonType2' is
private
}
effectivement j'avais pas testé avec gcc, ça cause une erreur. C'est aussi
le vas avec VC++ 8 ("cannot access private class declared in class 'A'"),
mais VC++ 7.1 laisse passer.
Mais je comprends toujours pas pourquoi le 0 passe. Pour moi, les 2 lignes
étaitent équivalentes (0 avec et sans cast), car il y avait cast implicite.
Je pensais que le compilo considérait 0 comme un MonType1 / MonType2*
initialisé à 0, mais apparement non. Comment ça marche ? Où a lieu le cast
implicite ?
Lorsque je mets MonType2 à la place de MyType, aucun de ces 3 casts ne compile chez moi avec gcc 3.4.1 (dans un fonction non-membre - en l'occurrence main()), tout comme il se doit.
Oui je me suis trompé en copiant-collant, c'est MonType2:
class A { private: typedef int MonType1; // types privés class MonType2 {};
int main() { A a; a.Test1( 0 ); // OK a.Test2( 0 ); // OK a.Test1( static_cast<A::MonType1>( 0 ) ); // `class A::MonType1' is private a.Test2( static_cast<A::MonType2*>( 0 ) ); // `class A::MonType2' is private }
effectivement j'avais pas testé avec gcc, ça cause une erreur. C'est aussi le vas avec VC++ 8 ("cannot access private class declared in class 'A'"), mais VC++ 7.1 laisse passer. Mais je comprends toujours pas pourquoi le 0 passe. Pour moi, les 2 lignes étaitent équivalentes (0 avec et sans cast), car il y avait cast implicite. Je pensais que le compilo considérait 0 comme un MonType1 / MonType2* initialisé à 0, mais apparement non. Comment ça marche ? Où a lieu le cast implicite ?
-- Aurélien REGAT-BARREL
Aurélien REGAT-BARREL
Pourquoi le cast C++ est-il autorisé ?
Parce qu'il y a un bug dans ton compilateur ?
Oui, VC++ 7.1 en l'occurence. C'est corrigé dans VC++ 8 Beta 1.
En gros (et peut-être à des exceptions près), ce que fait le complateur, c'est :
-- il recherche le nom que tu as écris, et le « lie » à une déclaration, sans tenir compte de l'accès,
-- il vérifie l'accès -- si tu n'y a pas le droit, c'est une erreur, et finalement,
-- il interprète la sémantique de ce que tu as écris.
La première phase comprend aussi la résolution du surcharge, le cas échéant.
Mais l'origine de ma question c'est le cast implicite, il est effectué quand ? Après la vérification d'accès apparement. L'exemple suivant semble le confirmer:
class A { private: struct MonType { MonType( int ) {} };
public: void Test( const MonType & ) {}; };
int main() { A a; a.Test( int( 0 ) ); // OK }
c'est ce qui me surprend, car j'arrive à instancier un type privé depuis une fonction non membre...
Note bien comment ça s'applique à ton exemple « a.Test2( 0 ) ». Il n'y a aucun problème avec la première phase, évidemment ; le compilateur trouve des liaisons (des « bindings ») non ambigus pour tous les symboles dans l'expression. Ni avec la deuxième, non plus -- tous les liaisons s'attache à des noms publiques. Dans la troisième phase, il interprète la sémantique, ce qui fait bien intervenir le type MonType2, mais il a déjà fini avec la vérification des accès. Et lui (le compilateur), évidemment, connaît MonType2. Donc, pas de problème.
Ok. Je pensais que c'était un peu plus élaboré et qu'il faisait le cast implicite au moment de la recherche de binding. D'ailleurs, je vois pas trop comment il peut le faire après s'il est capable de trouver des bindings ambigus à ce moment là. Dans l'exemple ci-dessus, comment peut-il trouver la liaison entre int( 0 ) et A::MonType sans faire de cast lors de la phase 1 ? Et s'il fait le cast, pourquoi la phase 2 ne le contrôle pas ?
Quand tu écris la conversion de façon explicite, c'est autre chose. Le compilateur fait bien la recherche et la liaison du nom MonType2, et alors, ensuite, il vérifie les droits d'accès. Et puisque tu n'as pas le droit d'utiliser ce symbol, il râle. Ou au moins, il doit râler.
-- Aurélien REGAT-BARREL
Pourquoi le cast C++ est-il autorisé ?
Parce qu'il y a un bug dans ton compilateur ?
Oui, VC++ 7.1 en l'occurence. C'est corrigé dans VC++ 8 Beta 1.
En gros (et peut-être à des exceptions près), ce que fait le
complateur, c'est :
-- il recherche le nom que tu as écris, et le « lie » à une
déclaration, sans tenir compte de l'accès,
-- il vérifie l'accès -- si tu n'y a pas le droit, c'est une
erreur, et finalement,
-- il interprète la sémantique de ce que tu as écris.
La première phase comprend aussi la résolution du surcharge, le
cas échéant.
Mais l'origine de ma question c'est le cast implicite, il est effectué quand
? Après la vérification d'accès apparement.
L'exemple suivant semble le confirmer:
class A
{
private:
struct MonType
{
MonType( int ) {}
};
public:
void Test( const MonType & ) {};
};
int main()
{
A a;
a.Test( int( 0 ) ); // OK
}
c'est ce qui me surprend, car j'arrive à instancier un type privé depuis
une fonction non membre...
Note bien comment ça s'applique à ton exemple « a.Test2( 0 ) ».
Il n'y a aucun problème avec la première phase, évidemment ; le
compilateur trouve des liaisons (des « bindings ») non ambigus
pour tous les symboles dans l'expression. Ni avec la deuxième,
non plus -- tous les liaisons s'attache à des noms publiques.
Dans la troisième phase, il interprète la sémantique, ce qui
fait bien intervenir le type MonType2, mais il a déjà fini avec
la vérification des accès. Et lui (le compilateur), évidemment,
connaît MonType2. Donc, pas de problème.
Ok. Je pensais que c'était un peu plus élaboré et qu'il faisait le cast
implicite au moment de la recherche de binding. D'ailleurs, je vois pas trop
comment il peut le faire après s'il est capable de trouver des bindings
ambigus à ce moment là. Dans l'exemple ci-dessus, comment peut-il trouver la
liaison entre int( 0 ) et A::MonType sans faire de cast lors de la phase 1 ?
Et s'il fait le cast, pourquoi la phase 2 ne le contrôle pas ?
Quand tu écris la conversion de façon explicite, c'est autre
chose. Le compilateur fait bien la recherche et la liaison du
nom MonType2, et alors, ensuite, il vérifie les droits d'accès.
Et puisque tu n'as pas le droit d'utiliser ce symbol, il râle.
Ou au moins, il doit râler.
Oui, VC++ 7.1 en l'occurence. C'est corrigé dans VC++ 8 Beta 1.
En gros (et peut-être à des exceptions près), ce que fait le complateur, c'est :
-- il recherche le nom que tu as écris, et le « lie » à une déclaration, sans tenir compte de l'accès,
-- il vérifie l'accès -- si tu n'y a pas le droit, c'est une erreur, et finalement,
-- il interprète la sémantique de ce que tu as écris.
La première phase comprend aussi la résolution du surcharge, le cas échéant.
Mais l'origine de ma question c'est le cast implicite, il est effectué quand ? Après la vérification d'accès apparement. L'exemple suivant semble le confirmer:
class A { private: struct MonType { MonType( int ) {} };
public: void Test( const MonType & ) {}; };
int main() { A a; a.Test( int( 0 ) ); // OK }
c'est ce qui me surprend, car j'arrive à instancier un type privé depuis une fonction non membre...
Note bien comment ça s'applique à ton exemple « a.Test2( 0 ) ». Il n'y a aucun problème avec la première phase, évidemment ; le compilateur trouve des liaisons (des « bindings ») non ambigus pour tous les symboles dans l'expression. Ni avec la deuxième, non plus -- tous les liaisons s'attache à des noms publiques. Dans la troisième phase, il interprète la sémantique, ce qui fait bien intervenir le type MonType2, mais il a déjà fini avec la vérification des accès. Et lui (le compilateur), évidemment, connaît MonType2. Donc, pas de problème.
Ok. Je pensais que c'était un peu plus élaboré et qu'il faisait le cast implicite au moment de la recherche de binding. D'ailleurs, je vois pas trop comment il peut le faire après s'il est capable de trouver des bindings ambigus à ce moment là. Dans l'exemple ci-dessus, comment peut-il trouver la liaison entre int( 0 ) et A::MonType sans faire de cast lors de la phase 1 ? Et s'il fait le cast, pourquoi la phase 2 ne le contrôle pas ?
Quand tu écris la conversion de façon explicite, c'est autre chose. Le compilateur fait bien la recherche et la liaison du nom MonType2, et alors, ensuite, il vérifie les droits d'accès. Et puisque tu n'as pas le droit d'utiliser ce symbol, il râle. Ou au moins, il doit râler.
-- Aurélien REGAT-BARREL
kanze
Aurélien REGAT-BARREL wrote:
[...]
En gros (et peut-être à des exceptions près), ce que fait le complateur, c'est :
-- il recherche le nom que tu as écris, et le « lie » à une déclaration, sans tenir compte de l'accès,
-- il vérifie l'accès -- si tu n'y a pas le droit, c'est une erreur, et finalement,
-- il interprète la sémantique de ce que tu as écris.
La première phase comprend aussi la résolution du surcharge, le cas échéant.
Mais l'origine de ma question c'est le cast implicite, il est effectué quand ?
C'est de l'interprètation sémantique. Il n'entre en jeux qu'une fois que le compilateur essaie d'évaluer ce que signifie l'expression. Qu'il a donc fait la liaison de tous les symboles, et en a validé la légalité de l'accès.
Après la vérification d'accès apparement. L'exemple suivant semble le confirmer:
class A { private: struct MonType { MonType( int ) {} };
public: void Test( const MonType & ) {}; };
int main() { A a; a.Test( int( 0 ) ); // OK }
c'est ce qui me surprend, car j'arrive à instancier un type privé depuis une fonction non membre...
Une fois de plus : les droits d'accès portent sur le nom. Il y beaucoup de façons à accéder à des membres privés : par un pointeur ou une référence, par exemple, ou si une fonction virtuelle est privé dans la classe dérivée, mais publique dans la classe de base.
Note bien comment ça s'applique à ton exemple « a.Test2( 0 ) ». Il n'y a aucun problème avec la première phase, évidemment ; le compilateur trouve des liaisons (des « bindings ») non ambigus pour tous les symboles dans l'expression. Ni avec la deuxième, non plus -- tous les liaisons s'attache à des noms publiques. Dans la troisième phase, il interprète la sémantique, ce qui fait bien intervenir le type MonType2, mais il a déjà fini avec la vérification des accès. Et lui (le compilateur), évidemment, connaît MonType2. Donc, pas de problème.
Ok. Je pensais que c'était un peu plus élaboré et qu'il faisait le cast implicite au moment de la recherche de binding.
Globalement, je ne crois pas que ça serait possible. Dans le cas général, il faut qu'il ait fait les bindings pour savoir s'il faut un cast.
D'ailleurs, je vois pas trop comment il peut le faire après s'il est capable de trouver des bindings ambigus à ce moment là. Dans l'exemple ci-dessus, comment peut-il trouver la liaison entre int( 0 ) et A::MonType sans faire de cast lors de la phase 1 ?
Il n'y a pas de liaison entre int( 0 ) et A::MonType lors de la phase 1. Les paramètres n'entrevient pas dans la résolution du name binding. Lors de la phase 1, le compilateur associe A à la définition de la classe, MonType à la déclaration de la fonction qui se trouve dans A, et int à un mot clé. Chacun séparément. (Évidemment, ce n'est pas 100% séparément, parce qu'après A::, il ne va chercher MonType que dans la portée désignée par A. Mes ça se limite à des opérations qui jouent sur la portée.)
Dans le cas du surcharge des fonctions, on a un cas spécial (avec beaucoup de règles spéciales) qui permet un binding à un ensemble de déclarations dans la première phase -- mais même là, il y a des limites, et il faut que 1) tous les déclarations soient dans la même portée, et que toutes les déclarations déclarent des fonctions. Mais l'essentiel reste. Le compilateur effectue le binding symbole par symbole, sans régarder le contexte autour du symbole. (Il y a une ou deux exceptions, je crois -- surtout un hack pour la compatibilité C, mais ils n'entrent pas en jeu ici.)
Et s'il fait le cast, pourquoi la phase 2 ne le contrôle pas ?
Parce que la résolution du surcharge n'a lieu que dans la phase 3. Avant la phase 3, le compilateur n'a aucune idée sur le type ou le nombre de paramètres de la fonction. Tout ce qu'il sait, c'est où trouver la déclaration de la fonction quand il en aura besoin.
-- 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
Aurélien REGAT-BARREL wrote:
[...]
En gros (et peut-être à des exceptions près), ce que fait le
complateur, c'est :
-- il recherche le nom que tu as écris, et le « lie » à une
déclaration, sans tenir compte de l'accès,
-- il vérifie l'accès -- si tu n'y a pas le droit, c'est une
erreur, et finalement,
-- il interprète la sémantique de ce que tu as écris.
La première phase comprend aussi la résolution du surcharge,
le cas échéant.
Mais l'origine de ma question c'est le cast implicite, il est
effectué quand ?
C'est de l'interprètation sémantique. Il n'entre en jeux qu'une
fois que le compilateur essaie d'évaluer ce que signifie
l'expression. Qu'il a donc fait la liaison de tous les symboles,
et en a validé la légalité de l'accès.
Après la vérification d'accès apparement. L'exemple suivant
semble le confirmer:
class A
{
private:
struct MonType
{
MonType( int ) {}
};
public:
void Test( const MonType & ) {};
};
int main()
{
A a;
a.Test( int( 0 ) ); // OK
}
c'est ce qui me surprend, car j'arrive à instancier un type
privé depuis une fonction non membre...
Une fois de plus : les droits d'accès portent sur le nom. Il y
beaucoup de façons à accéder à des membres privés : par un
pointeur ou une référence, par exemple, ou si une fonction
virtuelle est privé dans la classe dérivée, mais publique dans
la classe de base.
Note bien comment ça s'applique à ton exemple « a.Test2( 0 ) ».
Il n'y a aucun problème avec la première phase, évidemment ; le
compilateur trouve des liaisons (des « bindings ») non ambigus
pour tous les symboles dans l'expression. Ni avec la deuxième,
non plus -- tous les liaisons s'attache à des noms publiques.
Dans la troisième phase, il interprète la sémantique, ce qui
fait bien intervenir le type MonType2, mais il a déjà fini avec
la vérification des accès. Et lui (le compilateur), évidemment,
connaît MonType2. Donc, pas de problème.
Ok. Je pensais que c'était un peu plus élaboré et qu'il
faisait le cast implicite au moment de la recherche de
binding.
Globalement, je ne crois pas que ça serait possible. Dans le cas
général, il faut qu'il ait fait les bindings pour savoir s'il
faut un cast.
D'ailleurs, je vois pas trop comment il peut le faire
après s'il est capable de trouver des bindings ambigus à ce
moment là. Dans l'exemple ci-dessus, comment peut-il trouver
la liaison entre int( 0 ) et A::MonType sans faire de cast
lors de la phase 1 ?
Il n'y a pas de liaison entre int( 0 ) et A::MonType lors de la
phase 1. Les paramètres n'entrevient pas dans la résolution du
name binding. Lors de la phase 1, le compilateur associe A à la
définition de la classe, MonType à la déclaration de la fonction
qui se trouve dans A, et int à un mot clé. Chacun séparément.
(Évidemment, ce n'est pas 100% séparément, parce qu'après A::,
il ne va chercher MonType que dans la portée désignée par A.
Mes ça se limite à des opérations qui jouent sur la portée.)
Dans le cas du surcharge des fonctions, on a un cas spécial
(avec beaucoup de règles spéciales) qui permet un binding à un
ensemble de déclarations dans la première phase -- mais même là,
il y a des limites, et il faut que 1) tous les déclarations
soient dans la même portée, et que toutes les déclarations
déclarent des fonctions. Mais l'essentiel reste. Le compilateur
effectue le binding symbole par symbole, sans régarder le
contexte autour du symbole. (Il y a une ou deux exceptions, je
crois -- surtout un hack pour la compatibilité C, mais ils
n'entrent pas en jeu ici.)
Et s'il fait le cast, pourquoi la phase 2 ne le contrôle pas ?
Parce que la résolution du surcharge n'a lieu que dans la phase
3. Avant la phase 3, le compilateur n'a aucune idée sur le type
ou le nombre de paramètres de la fonction. Tout ce qu'il sait,
c'est où trouver la déclaration de la fonction quand il en
aura besoin.
--
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
En gros (et peut-être à des exceptions près), ce que fait le complateur, c'est :
-- il recherche le nom que tu as écris, et le « lie » à une déclaration, sans tenir compte de l'accès,
-- il vérifie l'accès -- si tu n'y a pas le droit, c'est une erreur, et finalement,
-- il interprète la sémantique de ce que tu as écris.
La première phase comprend aussi la résolution du surcharge, le cas échéant.
Mais l'origine de ma question c'est le cast implicite, il est effectué quand ?
C'est de l'interprètation sémantique. Il n'entre en jeux qu'une fois que le compilateur essaie d'évaluer ce que signifie l'expression. Qu'il a donc fait la liaison de tous les symboles, et en a validé la légalité de l'accès.
Après la vérification d'accès apparement. L'exemple suivant semble le confirmer:
class A { private: struct MonType { MonType( int ) {} };
public: void Test( const MonType & ) {}; };
int main() { A a; a.Test( int( 0 ) ); // OK }
c'est ce qui me surprend, car j'arrive à instancier un type privé depuis une fonction non membre...
Une fois de plus : les droits d'accès portent sur le nom. Il y beaucoup de façons à accéder à des membres privés : par un pointeur ou une référence, par exemple, ou si une fonction virtuelle est privé dans la classe dérivée, mais publique dans la classe de base.
Note bien comment ça s'applique à ton exemple « a.Test2( 0 ) ». Il n'y a aucun problème avec la première phase, évidemment ; le compilateur trouve des liaisons (des « bindings ») non ambigus pour tous les symboles dans l'expression. Ni avec la deuxième, non plus -- tous les liaisons s'attache à des noms publiques. Dans la troisième phase, il interprète la sémantique, ce qui fait bien intervenir le type MonType2, mais il a déjà fini avec la vérification des accès. Et lui (le compilateur), évidemment, connaît MonType2. Donc, pas de problème.
Ok. Je pensais que c'était un peu plus élaboré et qu'il faisait le cast implicite au moment de la recherche de binding.
Globalement, je ne crois pas que ça serait possible. Dans le cas général, il faut qu'il ait fait les bindings pour savoir s'il faut un cast.
D'ailleurs, je vois pas trop comment il peut le faire après s'il est capable de trouver des bindings ambigus à ce moment là. Dans l'exemple ci-dessus, comment peut-il trouver la liaison entre int( 0 ) et A::MonType sans faire de cast lors de la phase 1 ?
Il n'y a pas de liaison entre int( 0 ) et A::MonType lors de la phase 1. Les paramètres n'entrevient pas dans la résolution du name binding. Lors de la phase 1, le compilateur associe A à la définition de la classe, MonType à la déclaration de la fonction qui se trouve dans A, et int à un mot clé. Chacun séparément. (Évidemment, ce n'est pas 100% séparément, parce qu'après A::, il ne va chercher MonType que dans la portée désignée par A. Mes ça se limite à des opérations qui jouent sur la portée.)
Dans le cas du surcharge des fonctions, on a un cas spécial (avec beaucoup de règles spéciales) qui permet un binding à un ensemble de déclarations dans la première phase -- mais même là, il y a des limites, et il faut que 1) tous les déclarations soient dans la même portée, et que toutes les déclarations déclarent des fonctions. Mais l'essentiel reste. Le compilateur effectue le binding symbole par symbole, sans régarder le contexte autour du symbole. (Il y a une ou deux exceptions, je crois -- surtout un hack pour la compatibilité C, mais ils n'entrent pas en jeu ici.)
Et s'il fait le cast, pourquoi la phase 2 ne le contrôle pas ?
Parce que la résolution du surcharge n'a lieu que dans la phase 3. Avant la phase 3, le compilateur n'a aucune idée sur le type ou le nombre de paramètres de la fonction. Tout ce qu'il sait, c'est où trouver la déclaration de la fonction quand il en aura besoin.
-- 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
Aurelien REGAT-BARREL
Ok tout est clair. Merci beaucoup pour ces explications.
-- Aurélien REGAT-BARREL
Ok tout est clair.
Merci beaucoup pour ces explications.