Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

assurer l'héritage d'un template

25 réponses
Avatar
jeremie fouche
Bonsoir
Je ne savais pas trop quel titre donner à mon post. Ce que je souhaite,
c'est assurer qu'un membre template hérite d'une classe donnée.

un bon exemple vaut mieux qu'un long discourt :

#include <iostream>

class A
{
public:
A() {}
void Do() { std::cout << "A" << std::endl; }
};

class B : public A
{
public:
B() {}
void Do() { std::cout << "B" << std::endl; }
};

class C
{
public:
C() {}
void Do() { std::cout << "C" << std::endl; }
};

class D
{
public:
D() {}

void Test()
{
Do<B>(); // OK, ca doit compiler
Do<A>(); // ne doit pas compiler
Do<C>(); // ne doit pas compiler
}

// template <class T : public A> ne fonctionne pas, dommage
template <class T>
void Do(void)
{
T t;
t.Do();
}
};

int main()
{
D d;
d.Test();
return 0;
}

C'est possible ?
Si oui, comment ?
Merci
--
Jérémie

10 réponses

1 2 3
Avatar
Sylvain SF
jeremie fouche wrote on 09/05/2008 00:21:
Bonsoir
Je ne savais pas trop quel titre donner à mon post. Ce que je souhaite,
c'est assurer qu'un membre template hérite d'une classe donnée.


dans votre exemple, ni la classe D, ni sa fonction membre 'Do'
"n'hérite" de la classe B ou A ou C. la garantie serait plus
comment s'assurer que la méthode Do reçoit un paramètre donnée.
votre question est donc: comment définir une fonction paramétrable
qui n'accepte qu'un un seul type de paramètre donné ?!

êtes-vous sur que cela a du sens ? ou que votre contrainte est
clairement expliquée ?

Sylvain.

Avatar
jeremie fouche
jeremie fouche wrote on 09/05/2008 00:21:
Bonsoir
Je ne savais pas trop quel titre donner à mon post. Ce que je souhaite,
c'est assurer qu'un membre template hérite d'une classe donnée.


dans votre exemple, ni la classe D, ni sa fonction membre 'Do'
"n'hérite" de la classe B ou A ou C. la garantie serait plus
comment s'assurer que la méthode Do reçoit un paramètre donnée.
votre question est donc: comment définir une fonction paramétrable
qui n'accepte qu'un un seul type de paramètre donné ?!


Absolument.

êtes-vous sur que cela a du sens ? ou que votre contrainte est
clairement expliquée ?


Je pensais, avec l'exemple donné.
Je souhaite refuser d'utiliser la méthode D::Do avec autre chose qu'une
classe héritant de A.
C'est plus pour apprendre, car aujourd'hui, en laissant le code tel
quel, cela fonctionne correctement dans mon produit. Je voudrais juste
savoir si c'est possible ou non.
Merci pour le recadrage sur la question
--
Jérémie


Avatar
Fabien LE LEZ
On Fri, 09 May 2008 00:21:26 +0200, jeremie fouche :

void Do(void)


Note en passant : le deuxième "void" est totalement inutile, et
typique d'un code en C. En C++ on écrira plutôt :

void Do()



class B : public A
Do<B>(); // OK, ca doit compiler
Do<A>(); // ne doit pas compiler
Do<C>(); // ne doit pas compiler


En résumé :

- si T = A, erreur de compilation
- sinon, si T dérive de A, OK
- sinon, erreur

C'est bien ça ?

Ça ressemble fort aux sections 2.7 et 2.1 de "Modern C++ Design"
(Alexandrescu).


Apparemment que le code suivant répond à la question :

#include "static_check.h"
#include "TypeManip.h"

class D
{
public:
void Test()
{
Do<B>(); // OK, ca doit compiler
//Do<A>(); // ne doit pas compiler
//Do<C>(); // ne doit pas compiler
}

public:
template <class T>
void Do()
{
STATIC_CHECK (SUPERSUBCLASS_STRICT (A, T), T_doit_deriver_de_A);
...
}
};

Les deux .h se trouvent dans Loki.
La version d'origine (basée sur le livre, et sur laquelle se base le
code ci-dessus) se trouve à l'adresse
<http://www.awprofessional.com/content/images/0201704315/sourcecode/loki.zip>.
Pour le reste, cf <http://en.wikipedia.org/wiki/Loki_%28C%2B%2B%29>.

Avatar
Fabien LE LEZ
On Fri, 09 May 2008 00:44:51 +0200, jeremie fouche :

Je souhaite refuser d'utiliser la méthode D::Do avec autre chose qu'une
classe héritant de A.


Là où j'ai du mal à comprendre le sens, c'est que tu acceptes B mais
refuses A.

Avatar
Fabien LE LEZ
On Fri, 09 May 2008 00:57:41 +0200, Fabien LE LEZ :

Apparemment que le code suivant


Ja parler la France aussi bien que Johnny...
Désolé.

Avatar
Sylvain SF
Fabien LE LEZ wrote on 09/05/2008 00:57:

template <class T> void Do()
{
STATIC_CHECK (SUPERSUBCLASS_STRICT (A, T), T_doit_deriver_de_A);
...
}


je pensais bien à un typeid mais les infos ne sont disponibles
qu'au runtime pas à la compil., c'est le cas ici ?
(j'ai parcouru le wiki mais pas éplucher tout le zip).

"T_doit_deriver_de_A" correspond à quoi ? (juste un bool utilisé
par STATIC_CHECK pour comparer ses 2 membres ?)

Sylvain.

Avatar
Sylvain SF
Sylvain SF wrote on 09/05/2008 02:09:

c'est le cas ici ?


j'ai trouvé mes réponses ... et je ne les ai pas comprises!

Sylvain.

Avatar
Fabien LE LEZ
On Fri, 09 May 2008 02:09:53 +0200, "Sylvain SF" :

je pensais bien à un typeid mais les infos ne sont disponibles
qu'au runtime pas à la compil., c'est le cas ici ?


Non, non, ici tout est statique.
L'OP voulait un système tel que la ligne "Do<B>();" compile, et les
lignes "Do<A>();" et "Do<C>();" ne compilent pas. C'est le cas : les
deux derniers déclenchent une erreur de compilation.

"SUPERSUBCLASS_STRICT (A, T)" est une valeur, calculée par le
compilateur, égale à 1 si T dérive (strictement) de A, et à 0 sinon.

"STATIC_CHECK (true, ..." est une no-op.
"STATIC_CHECK (false, ..." est un code incorrect.

En fait, on pourrait l'implémenter comme ceci :

#define STATIC_CHECK(x) { char dummy[x]; }
Si x vaut 0, le code n'est pas correct, et le compilo râle ; si x>0,
ce code ne fait rien d'utile.


C'est très différent de assert(x), qui ne déclenche jamais d'erreur de
compilation (mais peut parfois arrêter le programme à l'exécution.)


"T_doit_deriver_de_A" correspond à quoi ?


C'est censé être le message d'erreur qui s'affiche. En pratique, ça ne
semble pas fonctionner (avec Comeau), et je n'ai pas trop le courage
d'aller voir pourquoi.
Tu peux mettre n'importe quel identifiant à la place.


En passant, je rappelle que tout ça sort du bouquin "Modern C++
Design" (Alexandrescu), dont je conseille fortement la lecture. C'est
le livre qui m'a fait réellement comprendre toute la puissance des
templates.

Avatar
James Kanze
On May 9, 2:09 am, "Sylvain SF" wrote:
Fabien LE LEZ wrote on 09/05/2008 00:57:

template <class T> void Do()
{
STATIC_CHECK (SUPERSUBCLASS_STRICT (A, T), T_doit_deriver_de_A);
...
}


je pensais bien à un typeid mais les infos ne sont disponibles
qu'au runtime pas à la compil., c'est le cas ici ?
(j'ai parcouru le wiki mais pas éplucher tout le zip).

"T_doit_deriver_de_A" correspond à quoi ? (juste un bool utilisé
par STATIC_CHECK pour comparer ses 2 membres ?)


Je te conseille le Vandevoorde et Josuttis : il décrit en
détail ce genre de chose, et c'est extrèmement bien écrit. Dans
ce cas-ci, grosso modo :

La principe de base, c'est de se servir de la résolution du
surcharge des fonctions (dont des fonctions templatées) pour
choisir selon les caractèristiques du type. Mais évidemment, on
ne veut pas réelement appeler les fonctions, ce qui de toute
façon ne se fera que lors de l'exécution. L'astuce, ici, c'est
de se servir de sizeof pour obtenir une constante de compilation
qui dépend de la résolution du surcharge : sizeof( func(...) )
vaut la taille du type du rétour de la fonction, et le
compilateur effectue bien la résolution du surcharge sans jamais
appeler la fonction.

Alors, pour commencer, on a besoin de deux types de taille
garantie différente, donc :

typedef char FalseType ; // sizeof( FalseType ) == 1, garanti
struct TrueType
{
char dummy[ 2 ] ;
} ; // sizeof( TrueType ) > 1, garanti

Note que c'est moins évident qu'on pourrait penser, parce que
la taille renvoyée par sizeof comprend un éventuel rembourrage
nécessaire pour l'alignement, et que la norme impose bien peu de
contraints sur les tailles rélatives. Ceci est la solution
classique, et je crois que c'est garanti, du fait que dans
TrueType, l'adresse de dummy[1] doit bien être &dummy[0]+1.

Ensuite, il faut définir l'ensemble des fonctions pour
discriminer ; commençons par un cas ultrasimple, où tu exiges A
ou dérivé d'A :

class A {} ; // Il faut qu'elle soit connu:-).

FalseType discrimintator( ... ) ;
TrueType discrimintator( A* ) ;

template< typename T >
class SeulementDeriveeD_A
{
char dummy[ sizeof( discrimintator( (T*)( 0 ) ) ) - 1 ] ;
} ;

Note bien que si tu instancies SeulementDeriveeD_A avec A, ou
une classe dérivée d'A, la résolution du surcharge dans la
définition de dummy va choisir la deuxième déclaration de
discrimintator, sinon la première. Note bien aussi le choix de
paramètres formels et l'expression du paramètre réel. On fait
attention d'éviter le moindre exigeance supplémentaire, genre
copiable, ou un constructeur sans paramètres. Si on peut
initialiser un A* avec un T*, le compilateur choisit
discrimintator( A* ), parce que tout autre choix est préférable
à utiliser le ... Du coup, le sizeof renvoie quelque chose
supérieur à 1, et le code reste légal. Si l'appel de
discrimintator( A* ) n'est pas légal avec un T*, le compilateur
n'a pas le choix, il choisit discrimintator( ... ), le sizeof
renvoie 1, on retranche 1, et on a une déclaration d'un tableau
à taille 0. Et donc, une erreur à la compilation.

Ici, j'ai laissé la déclaration dans la classe même, pour rester
simple. Ce qui augment la taille de la classe. La plupart du
temps, on cherche à éviter cette augmentation de taille en
cachant la déclaration ailleurs, mais dans quelque chose qui est
certain d'être instantié lors de l'instantiation de la classe.
La plus sûr, c'est probablement de le coller dans une union
anonyme avec un autre élément de la classe -- une autre
solution fréquente, c'est de le mettre dans une fonction inline
qui est appelée dans le destructeur (ce qui permet encore des
déclarations des pointeurs aux types non voulus, mais déclenche
une erreur dès qu'il y a un objet du type).

Enfin, on est prèsque arrivé où tu voulais. Il ne reste qu'à
écarter le cas où la classe est la classe de base même. Et c'est
assez facile à tester pour une classe précise :

template< typename T >
FalseType discrimintator( T* ) ;
TrueType discrimintator( A* ) ;

Pour tout type sauf A, l'instantiation du template
correspondrait exactement, tandis que la deuxième déclaration
exigera une conversion. C'est donc l'instantiation du template
qui est choisi. Pour A même, les deux correspond exactement, et
quand le compilateur a à choisir entre deux fonctions qui
correspond de la même dégrée, c'est le non-template qui a
précédance sur le template.

En combinant les deux, on a :

FalseType discrimintator( ... ) ;
template< typename B >
TrueType discrimintator( A*, B* ) ;
FalseType discrimintator( A*, A* ) ;

template< typename T >
class SeulementDeriveeD_A
{
char dummy[ sizeof( discrimintator( (T*)( 0 ), (T*)( 0 ) ) ) -
1 ] ;
} ;

Finalement, on cherche à jouer avec le nom de la variable, etc.,
pour avoir quelque chose dans le message d'erreur qui donne une
indication d'où se trouve la vraie erreur. Mais vue les
variations dans les messages d'erreur, ce n'est pas toujours
évident.

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


Avatar
jeremie fouche
On Fri, 09 May 2008 00:21:26 +0200, jeremie fouche :

void Do(void)


Note en passant : le deuxième "void" est totalement inutile, et
typique d'un code en C. En C++ on écrira plutôt :


ET oui, je sais bien, tu me l'as déjà expliqué, mais les habitudes ont
(encore un peu) la vie dure. Note que j'ai réussi pour les autres ;)

En résumé :

- si T = A, erreur de compilation
- sinon, si T dérive de A, OK
- sinon, erreur

C'est bien ça ?


Voui

Ça ressemble fort aux sections 2.7 et 2.1 de "Modern C++ Design"
(Alexandrescu).


Apparemment que le code suivant répond à la question :...


Merci pour vos réponses
En fait, je pensais qu'on pouvait faire un truc dans le genre sans
passer par de gros trucs.
--
Jérémie


1 2 3