Wie funktioniert der folgende Code?
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];
Beachten Sie, dass dies
B
eine private Basis ist. Wie funktioniert das?Beachten Sie, dass
operator B*()
const ist. Warum ist es wichtig?Warum ist
template<typename T> static yes check(D*, T);
besser alsstatic yes check(B*, int);
?
Hinweis : Es handelt sich um eine reduzierte Version (Makros werden entfernt) von boost::is_base_of
. Und das funktioniert auf einer Vielzahl von Compilern.
c++
templates
overloading
implicit-conversion
typetraits
Alexey Malistov
quelle
quelle
is_base_of
: ideone.com/T0C1V Es funktioniert jedoch nicht mit älteren GCC-Versionen (GCC4.3 funktioniert einwandfrei).is_base_of<Base,Base>::value
sollte seintrue
; dies kehrt zurückfalse
.Antworten:
Wenn sie verwandt sind
Nehmen wir für einen Moment an, dass dies
B
tatsächlich eine Basis von istD
. Für den Aufruf voncheck
sind dann beide Versionen realisierbar, daHost
sie inD*
und konvertiert werden könnenB*
. Es ist eine frei wählbare Konvertierungssequenz wie beschrieben13.3.3.1.2
ausHost<B, D>
anD*
undB*
verbunden. Um Konvertierungsfunktionen zu finden, die die Klasse konvertieren können, werden die folgenden Kandidatenfunktionen für die erstecheck
Funktion gemäß synthetisiert13.3.1.5/1
Die erste Konvertierungsfunktion ist kein Kandidat, da
B*
sie nicht konvertiert werden kannD*
.Für die zweite Funktion existieren folgende Kandidaten:
Dies sind die beiden Kandidaten für die Konvertierungsfunktion, die das Hostobjekt übernehmen. Der erste nimmt es als konstante Referenz und der zweite nicht. Somit ist die zweite eine bessere Übereinstimmung für das nicht konstante
*this
Objekt (das implizite Objektargument ) von13.3.3.2/3b1sb4
und wird zum KonvertierenB*
für die zweitecheck
Funktion verwendet.Wenn Sie die Konstante entfernen würden , hätten wir die folgenden Kandidaten
Dies würde bedeuten, dass wir nicht mehr nach Konstanz auswählen können. In einem normalen Überlastungsauflösungsszenario wäre der Aufruf jetzt mehrdeutig, da der Rückgabetyp normalerweise nicht an der Überlastungsauflösung beteiligt ist. Für Konvertierungsfunktionen gibt es jedoch eine Hintertür. Wenn zwei Konvertierungsfunktionen gleich gut sind, entscheidet der Rückgabetyp, wer der beste ist
13.3.3/1
. Wenn Sie also die Konstante entfernen würden, würde die erste verwendet, da sieB*
besser inB*
alsD*
in konvertiertB*
.Welche benutzerdefinierte Konvertierungssequenz ist nun besser? Die für die zweite oder die erste Prüffunktion? Die Regel ist, dass benutzerdefinierte Konvertierungssequenzen nur verglichen werden können, wenn sie dieselbe Konvertierungsfunktion oder denselben Konstruktor gemäß verwenden
13.3.3.2/3b2
. Genau das ist hier der Fall: Beide verwenden die zweite Konvertierungsfunktion. Beachten Sie, dass die const daher wichtig ist, da sie den Compiler zwingt, die zweite Konvertierungsfunktion zu übernehmen.Da können wir sie vergleichen - welches ist besser? Die Regel ist, dass die bessere Konvertierung vom Rückgabetyp der Konvertierungsfunktion in den Zieltyp (erneut von
13.3.3.2/3b2
) gewinnt . In diesem FallD*
konvertiert besser nachD*
als nachB*
. Damit ist die erste Funktion ausgewählt und wir erkennen die Vererbung!Beachten Sie, dass wir, da wir nie tatsächlich in eine Basisklasse konvertieren mussten, die private Vererbung erkennen können, da es nicht von der Form der Vererbung abhängt, ob wir von a
D*
nach a konvertieren könnenB*
4.10/3
Wenn sie nicht verwandt sind
Nehmen wir nun an, sie sind nicht durch Vererbung verbunden. Somit haben wir für die erste Funktion die folgenden Kandidaten
Und zum zweiten haben wir jetzt ein anderes Set
Da wir nicht konvertieren können
D*
,B*
wenn wir keine Vererbungsbeziehung haben, haben wir jetzt keine gemeinsame Konvertierungsfunktion zwischen den beiden benutzerdefinierten Konvertierungssequenzen! Daher wären wir nicht eindeutig, wenn nicht die erste Funktion eine Vorlage wäre. Vorlagen sind die zweite Wahl, wenn es eine Nicht-Vorlagenfunktion gibt, die entsprechend gut ist13.3.3/1
. Daher wählen wir die Nicht-Template-Funktion (zweite) aus und erkennen, dass zwischenB
undD
! Keine Vererbung besteht.quelle
std::is_base_of<...>
. Es ist alles unter der Haube.boost::
diese sicherstellen, dass diese Eigenschaften verfügbar sind, bevor sie verwendet werden. Und ich habe das Gefühl, dass es unter ihnen eine Art "Herausforderung angenommene" Mentalität gibt, Dinge ohne die Hilfe des Compilers zu implementieren :)Lassen Sie uns anhand der Schritte herausfinden, wie es funktioniert.
Beginnen Sie mit dem
sizeof(check(Host<B,D>(), int()))
Teil. Der Compiler kann schnell erkennen, dass diescheck(...)
ein Funktionsaufrufausdruck ist, daher muss die Überlastungsauflösung aktiviert werdencheck
. Es stehen zwei Kandidatenüberladungen zur Verfügung,template <typename T> yes check(D*, T);
undno check(B*, int);
. Wenn der erste gewählt wird, erhalten Siesizeof(yes)
sonstsizeof(no)
Schauen wir uns als nächstes die Überlastungsauflösung an. Die erste Überladung ist eine Vorlageninstanziierung
check<int> (D*, T=int)
und der zweite Kandidat istcheck(B*, int)
. Die tatsächlich angegebenen Argumente sindHost<B,D>
undint()
. Der zweite Parameter unterscheidet sie eindeutig nicht; es diente lediglich dazu, die erste Überladung zu einer Vorlage zu machen. Wir werden später sehen, warum der Vorlagenteil relevant ist.Schauen Sie sich nun die benötigten Konvertierungssequenzen an. Für die erste Überladung haben wir
Host<B,D>::operator D*
- eine benutzerdefinierte Konvertierung. Zum zweiten ist die Überlastung schwieriger. Wir brauchen ein B *, aber es gibt möglicherweise zwei Konvertierungssequenzen. Einer ist überHost<B,D>::operator B*() const
. Wenn (und nur wenn) B und D durch Vererbung zusammenhängen, existiert die KonvertierungssequenzHost<B,D>::operator D*()
+D*->B*
. Nehmen wir nun an, D erbt tatsächlich von B. Die beiden Konvertierungssequenzen sindHost<B,D> -> Host<B,D> const -> operator B* const -> B*
undHost<B,D> -> operator D* -> D* -> B*
.Also, für verwandte B und D
no check(<Host<B,D>(), int())
wäre mehrdeutig. Infolgedessen wird die Vorlageyes check<int>(D*, int)
ausgewählt. Wenn D jedoch nicht von B erbt,no check(<Host<B,D>(), int())
ist es nicht mehrdeutig. Zu diesem Zeitpunkt kann die Überlastungsauflösung nicht basierend auf der kürzesten Konvertierungssequenz erfolgen. Bei gleichen Konvertierungssequenzen bevorzugt die Überlastungsauflösung jedoch Funktionen ohne Vorlage, dno check(B*, int)
. H.Sie sehen jetzt, warum es keine Rolle spielt, dass die Vererbung privat ist: Diese Beziehung dient nur dazu, die
no check(Host<B,D>(), int())
Überlastungsauflösung zu beseitigen, bevor die Zugriffsprüfung stattfindet. Und Sie sehen auch, warum dasoperator B* const
const sein muss: Sonst gibt es keine Notwendigkeit für denHost<B,D> -> Host<B,D> const
Schritt, keine Mehrdeutigkeit undno check(B*, int)
würde immer gewählt werden.quelle
const
. Wenn Ihre Antwort wahr ist, wird neinconst
benötigt. Aber es ist nicht wahr. Entfernenconst
und Trick wird nicht funktionieren.no check(B*, int)
nicht mehr mehrdeutig.no check(B*, int)
, dann für verwandteB
undD
, wäre es nicht mehrdeutig. Der Compiler würdeoperator D*()
die Konvertierung eindeutig durchführen, da er keine Konstante hat. Es ist eher ein bisschen in der entgegengesetzten Richtung: Wenn Sie entfernen die const, Sie einführen ein Gefühl der Mehrdeutigkeit, die aber durch die Tatsache gelöst, dieoperator B*()
einen überlegenen Rückgabetyp bereitstellt , die keinen Zeiger Konvertierung brauchtB*
wie derD*
Fall ist.B*
aus der<Host<B,D>()
temporären zu erhalten.Das
private
Bit wird von vollständig ignoriert,is_base_of
da vor der Zugänglichkeitsprüfung eine Überlastungsauflösung auftritt.Sie können dies einfach überprüfen:
Gleiches gilt hier, die Tatsache, dass
B
es sich um eine private Basis handelt, verhindert nicht die Überprüfung, sondern nur die Konvertierung, aber wir fragen nie nach der tatsächlichen Konvertierung;)quelle
host
wird willkürlich inD*
oderB*
in den nicht bewerteten Ausdruck konvertiert . Aus irgendeinem GrundD*
istB*
unter bestimmten Bedingungen vorzuziehen .Es hat möglicherweise etwas mit der teilweisen Bestellung der Überlastungsauflösung zu tun. D * ist spezialisierter als B *, falls D von B stammt.
Die genauen Details sind ziemlich kompliziert. Sie müssen die Vorrangstellung verschiedener Überlastungsauflösungsregeln herausfinden. Teilbestellung ist eine. Längen / Arten von Konvertierungssequenzen sind eine andere. Wenn schließlich zwei realisierbare Funktionen als gleich gut angesehen werden, werden Nichtvorlagen gegenüber Funktionsvorlagen ausgewählt.
Ich musste nie nachsehen, wie diese Regeln interagieren. Es scheint jedoch, dass eine teilweise Reihenfolge die anderen Regeln für die Überlastungsauflösung dominiert. Wenn D nicht von B abgeleitet ist, gelten die Teilbestellregeln nicht und die Nichtvorlage ist attraktiver. Wenn D von B abgeleitet ist, setzt die Teilreihenfolge ein und macht die Funktionsvorlage attraktiver - wie es scheint.
Wenn die Vererbung privat ist: Der Code fordert niemals eine Konvertierung von D * nach B * an, die eine öffentliche Vererbung erfordern würde.
quelle
is_base_of
und die Schleifen gesehen habe, die die Mitwirkenden durchlaufen haben, um dies sicherzustellen.The exact details are rather complicated
- das ist der Punkt. Bitte erkläre. Ich möchte es wissen.Beachten Sie bei Ihrer zweiten Frage, dass Host ohne const schlecht geformt wäre, wenn es mit B == D instanziiert würde. Is_base_of ist jedoch so konzipiert, dass jede Klasse eine Basis für sich selbst ist, daher muss einer der Konvertierungsoperatoren sei const.
quelle