OVH Cloud OVH Cloud

Declaration anticipee d'une sous classe

15 réponses
Avatar
Marc Boyer
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

5 réponses

1 2
Avatar
Franck Branjonneau
Laurent Deniau écrivait:

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.


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):

(AssignableConcept<_Tp>::__constraints() [with _Tp = Switch::Port]).


Tu as fait un bug report ?

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



Avatar
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


Avatar
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

Avatar
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.

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.


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.

-D_GLIBCXX_DEBUG
-D_GLIBCXX_DEBUG_PEDANTIC -c -o fclc++.o fclc++.cc


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.

AMHA, c'est une signe de qualité.

(AssignableConcept<_Tp>::__constraints() [with _Tp = Switch::Port]).

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



Avatar
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



1 2