On May 9, 2:09 am, "Sylvain SF" boiteaspam.info> wrote:
> Fabien LE LEZ wrote on 09/05/2008 00:57:
>> template 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:james.kanze@
gmail.com
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