Je n'arrive pas à faire une déclaration anticipée d'une
sous-classe. Voilà une version simplifiée du problème:
j'ai des Switchs, des Ports dans les Switch, et des
Flots qui traversent ces ports, de Switch en Switch.
Un Switch possède les flows qu'il émet. Un flow a une
liste de pointeurs sur les Ports qu'il traverse.
class Switch;
class Switch::Port; // error: no type named `Port' in `struct Switch'
class Flow {
std::list<Switch::Port*> path;
};
class Switch {
public:
class Port {
std::vector<Flow> send;
};
};
Et ça ne compile pas... Il y a bien sur plusieurs solutions:
un vecteur de Flow* dans un Port, ou ne pas faire de Port
une sous-classe de Switch, mais une classe à part.
Mais y a-t-il une autre solution ?
Marc Boyer
--
Entre le fort et le faible, c'est la liberte qui opprime et le droit
qui libere. Henri Lacordaire, Dominicain
Je ne savais pas qu'on pouvait définir une sous-classe après l'avoir déclaré. Ce qui est encore plus étonnant, c'est que je peux instancier un vector de Port alors qu'il est juste déclaré mais pas définit... Ce n'est pas portable : les composants de la bibliothèque
standard demande des types complets.
Mais le type est complet au moment de l'instantiation.
Le temps n'est pas à prendre en compte. Seul l'espace est à prendre en compte : où est le point d'instanciation ?
En plus, on a 14.3.1-2[Note]
Je n'ai pas dit les templates demandent des types complets.
et 14.7.1-9 qui disent pas la meme chose que toi.
14.7.1-9 parle de l'instanciation d'une classe template ?
Pas chez moi : g++-4.0 -std=c++98 -W -Wall -Wwrite-strings -pedantic-errors -D_GLIBCXX_CONCEPT_CHECKS
Si tu enleves les concepts, ca marche.
;-)
template< typename _T > struct Vector { _T t_; };
struct S {
struct Incomplete; Vector< Incomplete > v_; };
Je pense que "l'erreur" provient du concept qui verifie 23.1-3 en utilisant une operation d'assignation sur le type (e.g. Ports):
Si le concept se verifie a la definition, effectivement les effets sont indefinis (17.4.3.6-2) parce que le type est incomplet. Si le concept est verifie a l'instantiation, il ne devraient pas.
Je cite : « In particular, the effects are undefined in the following cases: [..] -- if an incomplete type (3.9) is used as a template argument when *instantiating* a template component. »
l'emphase est mienne.
Pour autant que je sache, les « concept checks » faits par g++ sont implémentés par la bibliothèque.
Mais je pense qu'un expert pourrait nous eclairer sur le pourquoi du comment qui m'echappe.
C++ Templates: The Complete Guide, chapitre 10, et la lumière fut ;)
-- Franck
Laurent Deniau <laurent.deniau@cern.ch> écrivait:
Franck Branjonneau wrote:
Marc Boyer <Marc.Boyer@enseeiht.yahoo.fr.invalid> écrivait:
Je ne savais pas qu'on pouvait définir une sous-classe après l'avoir
déclaré. Ce qui est encore plus étonnant, c'est que je peux instancier
un vector de Port alors qu'il est juste déclaré mais pas définit...
Ce n'est pas portable : les composants de la bibliothèque
standard demande des types complets.
Mais le type est complet au moment de l'instantiation.
Le temps n'est pas à prendre en compte. Seul l'espace est à prendre en
compte : où est le point d'instanciation ?
En plus, on a 14.3.1-2[Note]
Je n'ai pas dit les templates demandent des types complets.
et 14.7.1-9 qui disent pas la meme chose que toi.
14.7.1-9 parle de l'instanciation d'une classe template ?
Pas chez moi :
g++-4.0 -std=c++98 -W -Wall -Wwrite-strings -pedantic-errors
-D_GLIBCXX_CONCEPT_CHECKS
Si tu enleves les concepts, ca marche.
;-)
template< typename _T > struct Vector { _T t_; };
struct S {
struct Incomplete;
Vector< Incomplete > v_;
};
Je pense que "l'erreur" provient du concept qui verifie 23.1-3 en
utilisant une operation d'assignation sur le type (e.g. Ports):
Si le concept se verifie a la definition, effectivement les effets
sont indefinis (17.4.3.6-2) parce que le type est incomplet. Si le
concept est verifie a l'instantiation, il ne devraient pas.
Je cite :
« In particular, the effects are undefined in the following cases:
[..]
-- if an incomplete type (3.9) is used as a template argument when
*instantiating* a template component. »
l'emphase est mienne.
Pour autant que je sache, les « concept checks » faits par g++ sont
implémentés par la bibliothèque.
Mais je pense qu'un expert pourrait nous eclairer sur le pourquoi
du comment qui m'echappe.
C++ Templates: The Complete Guide, chapitre 10, et la lumière fut ;)
Je ne savais pas qu'on pouvait définir une sous-classe après l'avoir déclaré. Ce qui est encore plus étonnant, c'est que je peux instancier un vector de Port alors qu'il est juste déclaré mais pas définit... Ce n'est pas portable : les composants de la bibliothèque
standard demande des types complets.
Mais le type est complet au moment de l'instantiation.
Le temps n'est pas à prendre en compte. Seul l'espace est à prendre en compte : où est le point d'instanciation ?
En plus, on a 14.3.1-2[Note]
Je n'ai pas dit les templates demandent des types complets.
et 14.7.1-9 qui disent pas la meme chose que toi.
14.7.1-9 parle de l'instanciation d'une classe template ?
Pas chez moi : g++-4.0 -std=c++98 -W -Wall -Wwrite-strings -pedantic-errors -D_GLIBCXX_CONCEPT_CHECKS
Si tu enleves les concepts, ca marche.
;-)
template< typename _T > struct Vector { _T t_; };
struct S {
struct Incomplete; Vector< Incomplete > v_; };
Je pense que "l'erreur" provient du concept qui verifie 23.1-3 en utilisant une operation d'assignation sur le type (e.g. Ports):
Si le concept se verifie a la definition, effectivement les effets sont indefinis (17.4.3.6-2) parce que le type est incomplet. Si le concept est verifie a l'instantiation, il ne devraient pas.
Je cite : « In particular, the effects are undefined in the following cases: [..] -- if an incomplete type (3.9) is used as a template argument when *instantiating* a template component. »
l'emphase est mienne.
Pour autant que je sache, les « concept checks » faits par g++ sont implémentés par la bibliothèque.
Mais je pense qu'un expert pourrait nous eclairer sur le pourquoi du comment qui m'echappe.
C++ Templates: The Complete Guide, chapitre 10, et la lumière fut ;)
-- Franck
Franck Branjonneau
Laurent Deniau écrivait:
Marc Boyer wrote:
Je ne savais pas qu'on pouvait définir une sous-classe après l'avoir déclaré. Ce qui est encore plus étonnant, c'est que je peux instancier un vector de Port alors qu'il est juste déclaré mais pas définit... #include <vector> class Switch { public: class Port; std::vector<Port> ports; };
// Tu peux meme faire:
Switch var; // ici
Modulo l'implémentation du standard que tu utilses.
class Flow { std::vector<Switch::Port*> path; }; class Switch::Port { std::vector<Flow> send; }; Ce code compile chez moi (gcc 3.3.5).
La difference entre translation units et instantiation units est precisee dans 2.1-8.
2.1-8 est sans intérêt ici.
En fait l'instanciation de "Switch var" sera consideree apres la definition de Switch::Port parce que la definition de Switch implique un template (std::vector<>).
L'instanciation de Switch var ?
Si tu remplace std::vector<Port> ports par Port port; ca ne marche plus...
Et avec Port* port; ça remarche...
-- Franck
Laurent Deniau <laurent.deniau@cern.ch> écrivait:
Marc Boyer wrote:
Je ne savais pas qu'on pouvait définir une sous-classe après l'avoir
déclaré. Ce qui est encore plus étonnant, c'est que je peux instancier
un vector de Port alors qu'il est juste déclaré mais pas définit...
#include <vector>
class Switch {
public:
class Port;
std::vector<Port> ports;
};
// Tu peux meme faire:
Switch var; // ici
Modulo l'implémentation du standard que tu utilses.
class Flow {
std::vector<Switch::Port*> path;
};
class Switch::Port { std::vector<Flow> send; };
Ce code compile chez moi (gcc 3.3.5).
La difference entre translation units et instantiation units est
precisee dans 2.1-8.
2.1-8 est sans intérêt ici.
En fait l'instanciation de "Switch var" sera consideree apres la
definition de Switch::Port parce que la definition de Switch
implique un template (std::vector<>).
L'instanciation de Switch var ?
Si tu remplace std::vector<Port> ports par Port port; ca ne marche
plus...
Je ne savais pas qu'on pouvait définir une sous-classe après l'avoir déclaré. Ce qui est encore plus étonnant, c'est que je peux instancier un vector de Port alors qu'il est juste déclaré mais pas définit... #include <vector> class Switch { public: class Port; std::vector<Port> ports; };
// Tu peux meme faire:
Switch var; // ici
Modulo l'implémentation du standard que tu utilses.
class Flow { std::vector<Switch::Port*> path; }; class Switch::Port { std::vector<Flow> send; }; Ce code compile chez moi (gcc 3.3.5).
La difference entre translation units et instantiation units est precisee dans 2.1-8.
2.1-8 est sans intérêt ici.
En fait l'instanciation de "Switch var" sera consideree apres la definition de Switch::Port parce que la definition de Switch implique un template (std::vector<>).
L'instanciation de Switch var ?
Si tu remplace std::vector<Port> ports par Port port; ca ne marche plus...
Et avec Port* port; ça remarche...
-- Franck
Franck Branjonneau
Fabien LE LEZ écrivait:
En fait, j'ai confondu "type pas du tout défini" (i.e. un forward-declaration uniquement) et "type en cours de définition" (i.e. un std::list<C> membre de C).
Le code ci-dessous me semble correct :
class C { std::list<C> lc; // ... };
Toujours pas... Où est le point d'insertion de std::list<C> ?
-- Franck
Fabien LE LEZ <gramster@gramster.com> écrivait:
En fait, j'ai confondu "type pas du tout défini" (i.e. un
forward-declaration uniquement) et "type en cours de définition" (i.e.
un std::list<C> membre de C).
Le code ci-dessous me semble correct :
class C
{
std::list<C> lc;
// ...
};
Toujours pas... Où est le point d'insertion de std::list<C> ?
En fait, j'ai confondu "type pas du tout défini" (i.e. un forward-declaration uniquement) et "type en cours de définition" (i.e. un std::list<C> membre de C).
Le code ci-dessous me semble correct :
class C { std::list<C> lc; // ... };
Toujours pas... Où est le point d'insertion de std::list<C> ?
-- Franck
kanze
Laurent Deniau wrote:
Franck Branjonneau wrote:
Marc Boyer écrivait:
Je ne savais pas qu'on pouvait définir une sous-classe après l'avoir déclaré. Ce qui est encore plus étonnant, c'est que je peux instancier un vector de Port alors qu'il est juste déclaré mais pas définit...
Ce n'est pas portable : les composants de la bibliothèque standard demande des types complets.
Mais le type est complet au moment de l'instantiation. En plus, on a 14.3.1-2[Note] et 14.7.1-9 qui disent pas la meme chose que toi.
Attention. Il y a deux choses distinctes. La norme n'exige pas, en général, qu'un type soit complet quand il s'agit de l'instantiation d'un template -- ça dépend de ce qu'on fait avec le type dans un template. Donc, si j'ai :
template< typename T > struct C1 { T* pt ; } ; template< typename T > struct C2 { T t ; } ;
Il faut bien que T soit un type complet lors de l'instantiation de C2, mais pas lors de l'instantiation de C1. (Le sens des passages que tu cites, c'est que le fait d'être l'instantiation d'un template n'impose pas en soi que le type soit complet. Il reste que l'instantiation doit respecter les règles qui s'appliquent en général aux définitions non-templatées.)
Dans le cas de std::list<>, en revanche, il ne s'agit pas de n'importe quel template. On ne connaît pas son implémentation ; on ne peut donc pas se baser sur son implémentation pour dire s'il faut que le type soit complet. La question revient donc à ce que la bibliothèque en question garantit. Et on rétrouve §17.4.3.6/2 : « In particular, the effects are undefined in the following cases: [...] -- if an incomplete type is used as a template argument when instantiating a template. »
En gros, la norme donne à l'implémentation le droit de faire :
template< typename T ... > class list { // ... T _M_valeur_pour_emmerder_l_utilisateur ; } ;
Dans le cas de list, je ne vois d'implémentation où ça serait raisonable. Dans d'autres cas, en revanche, c'est moins sûr. La norme n'a pas voulu entrer dans les détails, et a fait une règle générale que le type doit être complet quand on instantie un template dans la bibliothèque standard.
Comme j'ai dit ci-dessus, je ne vois pas pourquoi une implémentation de std::list<> exigerait que le type soit complet, d'après la nature de la chose. N'empèche que la norme dit que si le type n'est pas complet, c'est un comportement indéfini. G++ a la mérite de détecter l'erreur, plutôt que de la laisser trainer.
Je pense que "l'erreur" provient du concept qui verifie 23.1-3 en utilisant une operation d'assignation sur le type (e.g. Ports):
L'erreur provient du fait que le comportement est indéfini. Quoique fasse le compilateur, il a raison. Et quelqu'un de chez G++ a pensé que plutôt que de faire n'importe quoi, une erreur en bonne forme serait préférable, et a fait la nécessaire pour la générer.
Si le concept se verifie a la definition, effectivement les effets sont indefinis (17.4.3.6-2) parce que le type est incomplet. Si le concept est verifie a l'instantiation, il ne devraient pas. Mais je pense qu'un expert pourrait nous eclairer sur le pourquoi du comment qui m'echappe.
Le concept ici ne peut certainement pas se vérifie à la définition du template, parce que l'expression est on ne peut plus dépendante. Le concepte se vérifie à l'instantation, dans la contexte de l'instantiation. C-à-d, immédiatement *avant* la définition de la classe, voir §14.6.4.1/3.
-- 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
Laurent Deniau wrote:
Franck Branjonneau wrote:
Marc Boyer <Marc.Boyer@enseeiht.yahoo.fr.invalid> écrivait:
Je ne savais pas qu'on pouvait définir une sous-classe
après l'avoir
déclaré. Ce qui est encore plus étonnant, c'est que je peux
instancier un vector de Port alors qu'il est juste déclaré
mais pas définit...
Ce n'est pas portable : les composants de la bibliothèque
standard demande des types complets.
Mais le type est complet au moment de l'instantiation. En
plus, on a 14.3.1-2[Note] et 14.7.1-9 qui disent pas la meme
chose que toi.
Attention. Il y a deux choses distinctes. La norme n'exige pas,
en général, qu'un type soit complet quand il s'agit de
l'instantiation d'un template -- ça dépend de ce qu'on fait avec
le type dans un template. Donc, si j'ai :
template< typename T >
struct C1 { T* pt ; } ;
template< typename T >
struct C2 { T t ; } ;
Il faut bien que T soit un type complet lors de l'instantiation
de C2, mais pas lors de l'instantiation de C1. (Le sens des
passages que tu cites, c'est que le fait d'être l'instantiation
d'un template n'impose pas en soi que le type soit complet. Il
reste que l'instantiation doit respecter les règles qui
s'appliquent en général aux définitions non-templatées.)
Dans le cas de std::list<>, en revanche, il ne s'agit pas de
n'importe quel template. On ne connaît pas son implémentation ;
on ne peut donc pas se baser sur son implémentation pour dire
s'il faut que le type soit complet. La question revient donc à
ce que la bibliothèque en question garantit. Et on rétrouve
§17.4.3.6/2 : « In particular, the effects are undefined in the
following cases: [...] -- if an incomplete type is used as a
template argument when instantiating a template. »
En gros, la norme donne à l'implémentation le droit de faire :
template< typename T ... >
class list
{
// ...
T _M_valeur_pour_emmerder_l_utilisateur ;
} ;
Dans le cas de list, je ne vois d'implémentation où ça serait
raisonable. Dans d'autres cas, en revanche, c'est moins sûr. La
norme n'a pas voulu entrer dans les détails, et a fait une règle
générale que le type doit être complet quand on instantie un
template dans la bibliothèque standard.
Comme j'ai dit ci-dessus, je ne vois pas pourquoi une
implémentation de std::list<> exigerait que le type soit
complet, d'après la nature de la chose. N'empèche que la norme
dit que si le type n'est pas complet, c'est un comportement
indéfini. G++ a la mérite de détecter l'erreur, plutôt que de la
laisser trainer.
Je pense que "l'erreur" provient du concept qui verifie 23.1-3
en utilisant une operation d'assignation sur le type (e.g.
Ports):
L'erreur provient du fait que le comportement est indéfini.
Quoique fasse le compilateur, il a raison. Et quelqu'un de chez
G++ a pensé que plutôt que de faire n'importe quoi, une erreur
en bonne forme serait préférable, et a fait la nécessaire pour
la générer.
Si le concept se verifie a la definition, effectivement les
effets sont indefinis (17.4.3.6-2) parce que le type est
incomplet. Si le concept est verifie a l'instantiation, il ne
devraient pas. Mais je pense qu'un expert pourrait nous
eclairer sur le pourquoi du comment qui m'echappe.
Le concept ici ne peut certainement pas se vérifie à la
définition du template, parce que l'expression est on ne peut
plus dépendante. Le concepte se vérifie à l'instantation, dans
la contexte de l'instantiation. C-à-d, immédiatement *avant* la
définition de la classe, voir §14.6.4.1/3.
--
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
Je ne savais pas qu'on pouvait définir une sous-classe après l'avoir déclaré. Ce qui est encore plus étonnant, c'est que je peux instancier un vector de Port alors qu'il est juste déclaré mais pas définit...
Ce n'est pas portable : les composants de la bibliothèque standard demande des types complets.
Mais le type est complet au moment de l'instantiation. En plus, on a 14.3.1-2[Note] et 14.7.1-9 qui disent pas la meme chose que toi.
Attention. Il y a deux choses distinctes. La norme n'exige pas, en général, qu'un type soit complet quand il s'agit de l'instantiation d'un template -- ça dépend de ce qu'on fait avec le type dans un template. Donc, si j'ai :
template< typename T > struct C1 { T* pt ; } ; template< typename T > struct C2 { T t ; } ;
Il faut bien que T soit un type complet lors de l'instantiation de C2, mais pas lors de l'instantiation de C1. (Le sens des passages que tu cites, c'est que le fait d'être l'instantiation d'un template n'impose pas en soi que le type soit complet. Il reste que l'instantiation doit respecter les règles qui s'appliquent en général aux définitions non-templatées.)
Dans le cas de std::list<>, en revanche, il ne s'agit pas de n'importe quel template. On ne connaît pas son implémentation ; on ne peut donc pas se baser sur son implémentation pour dire s'il faut que le type soit complet. La question revient donc à ce que la bibliothèque en question garantit. Et on rétrouve §17.4.3.6/2 : « In particular, the effects are undefined in the following cases: [...] -- if an incomplete type is used as a template argument when instantiating a template. »
En gros, la norme donne à l'implémentation le droit de faire :
template< typename T ... > class list { // ... T _M_valeur_pour_emmerder_l_utilisateur ; } ;
Dans le cas de list, je ne vois d'implémentation où ça serait raisonable. Dans d'autres cas, en revanche, c'est moins sûr. La norme n'a pas voulu entrer dans les détails, et a fait une règle générale que le type doit être complet quand on instantie un template dans la bibliothèque standard.
Comme j'ai dit ci-dessus, je ne vois pas pourquoi une implémentation de std::list<> exigerait que le type soit complet, d'après la nature de la chose. N'empèche que la norme dit que si le type n'est pas complet, c'est un comportement indéfini. G++ a la mérite de détecter l'erreur, plutôt que de la laisser trainer.
Je pense que "l'erreur" provient du concept qui verifie 23.1-3 en utilisant une operation d'assignation sur le type (e.g. Ports):
L'erreur provient du fait que le comportement est indéfini. Quoique fasse le compilateur, il a raison. Et quelqu'un de chez G++ a pensé que plutôt que de faire n'importe quoi, une erreur en bonne forme serait préférable, et a fait la nécessaire pour la générer.
Si le concept se verifie a la definition, effectivement les effets sont indefinis (17.4.3.6-2) parce que le type est incomplet. Si le concept est verifie a l'instantiation, il ne devraient pas. Mais je pense qu'un expert pourrait nous eclairer sur le pourquoi du comment qui m'echappe.
Le concept ici ne peut certainement pas se vérifie à la définition du template, parce que l'expression est on ne peut plus dépendante. Le concepte se vérifie à l'instantation, dans la contexte de l'instantiation. C-à-d, immédiatement *avant* la définition de la classe, voir §14.6.4.1/3.
-- 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
kanze
Franck Branjonneau wrote:
Laurent Deniau écrivait:
Marc Boyer wrote:
Je ne savais pas qu'on pouvait définir une sous-classe après l'avoir déclaré. Ce qui est encore plus étonnant, c'est que je peux instancier un vector de Port alors qu'il est juste déclaré mais pas définit... #include <vector> class Switch { public: class Port; std::vector<Port> ports; };
// Tu peux meme faire:
Switch var; // ici
Modulo l'implémentation du standard que tu utilses.
Et à condition d'accepter un comportement indéfini.
Une implémentation a le droit de définir un comportement dans le cas des comportements indéfinis. C'est même possible qu'il y a des implémentations qui garantissent ceci, bien que je ne les connais pas. Mais sans une garantie explicite de l'implémentation, c'est un comportement indéfini, qui peut marcher aujourd'hui, mais pas demain.
D'après ce que je vois, g++ a décidé à donner une erreur dans ce cas de comportement indéfini, plutôt que de laisser un comportement aléatoire.
-- James Kanze GABI Software Conseils en informatique orientée objet/ Beratung in objektorientierter Datenverarbeitung 9
Franck Branjonneau wrote:
Laurent Deniau <laurent.deniau@cern.ch> écrivait:
Marc Boyer wrote:
Je ne savais pas qu'on pouvait définir une sous-classe
après l'avoir déclaré. Ce qui est encore plus étonnant,
c'est que je peux instancier un vector de Port alors
qu'il est juste déclaré mais pas définit...
#include <vector>
class Switch {
public:
class Port;
std::vector<Port> ports;
};
// Tu peux meme faire:
Switch var; // ici
Modulo l'implémentation du standard que tu utilses.
Et à condition d'accepter un comportement indéfini.
Une implémentation a le droit de définir un comportement dans le
cas des comportements indéfinis. C'est même possible qu'il y a
des implémentations qui garantissent ceci, bien que je ne les
connais pas. Mais sans une garantie explicite de
l'implémentation, c'est un comportement indéfini, qui peut
marcher aujourd'hui, mais pas demain.
D'après ce que je vois, g++ a décidé à donner une erreur dans ce
cas de comportement indéfini, plutôt que de laisser un
comportement aléatoire.
--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9
Je ne savais pas qu'on pouvait définir une sous-classe après l'avoir déclaré. Ce qui est encore plus étonnant, c'est que je peux instancier un vector de Port alors qu'il est juste déclaré mais pas définit... #include <vector> class Switch { public: class Port; std::vector<Port> ports; };
// Tu peux meme faire:
Switch var; // ici
Modulo l'implémentation du standard que tu utilses.
Et à condition d'accepter un comportement indéfini.
Une implémentation a le droit de définir un comportement dans le cas des comportements indéfinis. C'est même possible qu'il y a des implémentations qui garantissent ceci, bien que je ne les connais pas. Mais sans une garantie explicite de l'implémentation, c'est un comportement indéfini, qui peut marcher aujourd'hui, mais pas demain.
D'après ce que je vois, g++ a décidé à donner une erreur dans ce cas de comportement indéfini, plutôt que de laisser un comportement aléatoire.
-- James Kanze GABI Software Conseils en informatique orientée objet/ Beratung in objektorientierter Datenverarbeitung 9