Comment fonctionne le code suivant?
typedef char (&yes)[1];
typedef char (&no)[2];
template <typename B, typename D>
struct Host
{
operator B*() const;
operator D*();
};
template <typename B, typename D>
struct is_base_of
{
template <typename T>
static yes check(D*, T);
static no check(B*, int);
static const bool value = sizeof(check(Host<B,D>(), int())) == sizeof(yes);
};
//Test sample
class Base {};
class Derived : private Base {};
//Expression is true.
int test[is_base_of<Base,Derived>::value && !is_base_of<Derived,Base>::value];
Notez que
B
c'est une base privée. Comment cela marche-t-il?Notez que
operator B*()
c'est const. Pourquoi c'est important?Pourquoi est-ce
template<typename T> static yes check(D*, T);
mieux questatic yes check(B*, int);
?
Remarque : Il s'agit d'une version réduite (les macros sont supprimées) de boost::is_base_of
. Et cela fonctionne sur une large gamme de compilateurs.
c++
templates
overloading
implicit-conversion
typetraits
Alexey Malistov
la source
la source
is_base_of
: ideone.com/T0C1V Cela ne fonctionne pas avec les anciennes versions de GCC (GCC4.3 fonctionne bien).is_base_of<Base,Base>::value
devrait êtretrue
; cela revientfalse
.Réponses:
S'ils sont liés
Supposons un instant qu'il
B
s'agit en fait d'une base deD
. Ensuite, pour l'appel àcheck
, les deux versions sont viables car ellesHost
peuvent être converties enD*
etB*
. C'est une séquence de conversion définie par l'utilisateur comme décrit par13.3.3.1.2
deHost<B, D>
àD*
etB*
respectivement. Pour trouver des fonctions de conversion capables de convertir la classe, les fonctions candidates suivantes sont synthétisées pour la premièrecheck
fonction selon13.3.1.5/1
La première fonction de conversion n'est pas candidate, car elle
B*
ne peut pas être convertie enD*
.Pour la deuxième fonction, les candidats suivants existent:
Ce sont les deux candidats de fonction de conversion qui prennent l'objet hôte. Le premier le prend par référence const, et le second non. Ainsi, le second est une meilleure correspondance pour l'
*this
objet non-const (l' argument d'objet implicite ) par13.3.3.2/3b1sb4
et est utilisé pour convertir enB*
pour la secondecheck
fonction.Si vous supprimiez le const, nous aurions les candidats suivants
Cela signifierait que nous ne pouvons plus sélectionner par constness. Dans un scénario de résolution de surcharge ordinaire, l'appel serait désormais ambigu car normalement le type de retour ne participera pas à la résolution de surcharge. Pour les fonctions de conversion, cependant, il existe une porte dérobée. Si deux fonctions de conversion sont également bonnes, le type de retour de celles-ci décide de qui est le meilleur selon
13.3.3/1
. Ainsi, si vous supprimiez le const, alors le premier serait pris, carB*
convertit mieux enB*
queD*
enB*
.Quelle est la meilleure séquence de conversion définie par l'utilisateur? Celui de la deuxième ou de la première fonction de contrôle? La règle est que les séquences de conversion définies par l'utilisateur ne peuvent être comparées que si elles utilisent la même fonction de conversion ou le même constructeur que selon
13.3.3.2/3b2
. C'est exactement le cas ici: les deux utilisent la deuxième fonction de conversion. Notez qu'ainsi le const est important car il force le compilateur à prendre la deuxième fonction de conversion.Puisque nous pouvons les comparer, lequel est le meilleur? La règle est que la meilleure conversion du type de retour de la fonction de conversion vers le type de destination l'emporte (à nouveau par
13.3.3.2/3b2
). Dans ce cas,D*
convertit mieux enD*
qu'enB*
. Ainsi la première fonction est sélectionnée et on reconnaît l'héritage!Notez que puisque nous n'avons jamais eu besoin de convertir réellement en une classe de base, nous pouvons ainsi reconnaître l'héritage privé car la possibilité de convertir de a
D*
en aB*
ne dépend pas de la forme d'héritage selon4.10/3
S'ils ne sont pas liés
Supposons maintenant qu'ils ne soient pas liés par héritage. Ainsi pour la première fonction nous avons les candidats suivants
Et pour la seconde, nous avons maintenant un autre ensemble
Puisque nous ne pouvons pas convertir
D*
enB*
si nous n'avons pas de relation d'héritage, nous n'avons maintenant aucune fonction de conversion commune entre les deux séquences de conversion définies par l'utilisateur! Ainsi, nous serions ambigus si ce n'était du fait que la première fonction est un modèle. Les modèles sont le deuxième choix lorsqu'il existe une fonction non-modèle qui est tout aussi bonne selon13.3.3/1
. Ainsi, nous sélectionnons la fonction non-modèle (deuxième) et nous reconnaissons qu'il n'y a pas d'héritage entreB
etD
!la source
std::is_base_of<...>
. Tout est sous le capot.boost::
doivent s'assurer qu'elles ont ces intrinsèques disponibles avant de les utiliser. Et j'ai le sentiment qu'il y a une sorte de mentalité de "défi accepté" parmi eux pour implémenter des choses sans l'aide du compilateur :)Voyons comment cela fonctionne en regardant les étapes.
Commencez par la
sizeof(check(Host<B,D>(), int()))
pièce. Le compilateur peut voir rapidement qu'ilcheck(...)
s'agit d'une expression d'appel de fonction, il doit donc effectuer une résolution de surcharge surcheck
. Il existe deux surcharges candidates disponibles,template <typename T> yes check(D*, T);
etno check(B*, int);
. Si le premier est choisi, vous obtenezsizeof(yes)
, sinonsizeof(no)
Ensuite, regardons la résolution de surcharge. La première surcharge est une instanciation de modèle
check<int> (D*, T=int)
et le second candidat l'estcheck(B*, int)
. Les arguments réels fournis sontHost<B,D>
etint()
. Le deuxième paramètre ne les distingue clairement pas; cela a simplement servi à faire de la première surcharge un modèle. Nous verrons plus tard pourquoi la partie modèle est pertinente.Examinez maintenant les séquences de conversion nécessaires. Pour la première surcharge, nous avons
Host<B,D>::operator D*
- une conversion définie par l'utilisateur. Pour le second, la surcharge est plus délicate. Nous avons besoin d'un B *, mais il y a peut-être deux séquences de conversion. L'un est viaHost<B,D>::operator B*() const
. Si (et seulement si) B et D sont liés par héritage, la séquence de conversionHost<B,D>::operator D*()
+D*->B*
existera-t-elle. Supposons maintenant que D hérite effectivement de B. Les deux séquences de conversion sontHost<B,D> -> Host<B,D> const -> operator B* const -> B*
etHost<B,D> -> operator D* -> D* -> B*
.Donc, pour B et D liés,
no check(<Host<B,D>(), int())
serait ambigu. En conséquence, le modèleyes check<int>(D*, int)
est choisi. Cependant, si D n'hérite pas de B, alorsno check(<Host<B,D>(), int())
n'est pas ambigu. À ce stade, la résolution de surcharge ne peut pas se produire sur la base de la séquence de conversion la plus courte. Cependant, à séquences de conversion égales, la résolution de surcharge préfère les fonctions non-modèle, c'est-à-direno check(B*, int)
.Vous voyez maintenant pourquoi peu importe que l'héritage soit privé: cette relation ne sert qu'à éliminer
no check(Host<B,D>(), int())
de la résolution de surcharge avant que le contrôle d'accès ne se produise. Et vous voyez aussi pourquoi leoperator B* const
doit être const: sinon il n'y a pas besoin de l'Host<B,D> -> Host<B,D> const
étape, pas d'ambiguïté, etno check(B*, int)
serait toujours choisi.la source
const
. Si votre réponse est vraie, nonconst
. Mais ce n'est pas vrai. Supprimerconst
et tromper ne fonctionnera pas.no check(B*, int)
ne sont plus ambiguës.no check(B*, int)
, alors pour liéB
etD
, ce ne serait pas ambigu. Le compilateur choisirait sans ambiguïtéoperator D*()
d'effectuer la conversion car il n'a pas de const. C'est plutôt un peu dans la direction opposée: si vous supprimez le const, vous introduisez une certaine ambiguïté, mais qui est résolue par le fait qu'iloperator B*()
fournit un type de retour supérieur qui n'a pas besoin d'une conversion de pointeur pourB*
likeD*
do.B*
du<Host<B,D>()
temporaire.Le
private
bit est complètement ignoréis_base_of
car la résolution de surcharge se produit avant les vérifications d'accessibilité.Vous pouvez vérifier cela simplement:
Il en va de même ici, le fait qu'il
B
s'agisse d'une base privée n'empêche pas le contrôle d'avoir lieu, cela empêcherait seulement la conversion, mais nous ne demandons jamais la conversion proprement dite;)la source
host
est arbitrairement converti enD*
ouB*
dans l'expression non évaluée. Pour une raison quelconque,D*
est préférable àB*
certaines conditions.Cela a peut-être quelque chose à voir avec un ordre partiel de résolution de surcharge. D * est plus spécialisé que B * dans le cas où D dérive de B.
Les détails exacts sont plutôt compliqués. Vous devez déterminer les priorités de diverses règles de résolution de surcharge. La commande partielle en est une. Les longueurs / types de séquences de conversion en est un autre. Enfin, si deux fonctions viables sont jugées également bonnes, les non-modèles sont choisis par rapport aux modèles de fonctions.
Je n'ai jamais eu besoin de chercher comment ces règles interagissent. Mais il semble que l'ordre partiel domine les autres règles de résolution de surcharge. Lorsque D ne dérive pas de B, les règles de tri partiel ne s'appliquent pas et le non-modèle est plus attrayant. Lorsque D dérive de B, l'ordre partiel entre en jeu et rend le modèle de fonction plus attrayant - comme il semble.
Quant à l'héritage étant privé: le code ne demande jamais une conversion de D * en B * qui nécessiterait un héritage public.
la source
is_base_of
et les boucles que les contributeurs ont traversées pour assurer cela.The exact details are rather complicated
- c'est le but. S'il vous plaît, expliquez. Je veux savoir.À la suite de votre deuxième question, notez que si ce n'était pas pour const, Host serait mal formé s'il était instancié avec B == D.Mais is_base_of est conçu de telle sorte que chaque classe est une base d'elle-même, donc l'un des opérateurs de conversion doit être const.
la source