Wenn Ausdrucksvorlagen mithilfe von CRTP implementiert werden, verwendet die Klasse an der Spitze der Ausdruckshierarchie Down-Casting von Basis zu abgeleitet, um einige ihrer Operationen zu implementieren. Laut clang-3.5 ( -std=c++1y
) sollte dieser Downcast in constexpr
functions illegal sein:
GCC macht glücklich den Code zusammen. Wer hat Recht? Wenn Clang richtig ist, welche C ++ 14 Einschränkung für constexpr
macht diese Downcasting illegal?
Hier ist der MWE:
%Vor% Damit operator()
in base
eine gültige static_cast
ausführt, muss das am meisten abgeleitete Objekt, auf das this
zeigt, vom Typ Derived
(oder einer Unterklasse davon) sein. Die Mitglieder von e
haben jedoch den Typ base<derived>
, nicht derived
selbst. In der Zeile
m_a
hat den Typ base<derived>
und base<derived>::operator()
heißt - mit einem am meisten abgeleiteten Objekt vom Typ base<derived>
.
Daher versucht die Besetzung, *this
auf einen Verweis auf den Objekttyp zu übertragen, auf den sie sich nicht bezieht. Diese Operation hätte ein undefiniertes Verhalten, wie [expr.static.cast] / 2 beschreibt:
Ein Lvalue vom Typ " cv1
B
", wobei B ein Klassentyp ist, in den umgewandelt werden kann Geben Sie "reference to cv2D
" ein, wobeiD
eine Klasse ist abgeleitet (Klausel 10) vonB
[..]. Wenn das Objekt vom Typ " cv1 B" tatsächlich ein Unterobjekt eines Objekts vom TypD
ist, wird auf das Ergebnis verwiesen zum einschließenden Objekt vom TypD
. Ansonsten ist das Verhalten undefiniert.
Und danach gilt [expr.const] / 2:
Ein
e
ist ein Ausdruck für die Kernkonstante, es sei denn die Auswertung vone
nach den Regeln der abstrakten Maschine (1.9), würde einen der folgenden Ausdrücke auswerten:(2.5) - eine Operation, die ein undefiniertes Verhalten hätte
Stattdessen schreiben Sie foo
wie folgt um:
Und der Code funktioniert gut .
Mir scheint, dass Clang in diesem Fall recht hat. Der Typ von e
ist const expr<base<derived>, base<derived>>
, also haben m_a
und m_b
den Typ base<derived>
und nicht derived
. Mit anderen Worten, Sie haben geschnitten d
, wenn Sie es in m_a
und m_b
kopieren.
Hier ist eine anständige Überarbeitung Ihres ursprünglichen Codes. UB wird entfernt und es erstreckt sich gut:
%Vor% Grundsätzlich muss jedes base<X>
ein X
sein. Wenn Sie es speichern, speichern Sie es als X
, nicht als base<X>
. Wir können auf X
via base<X>::self()
in constexpr
Mode zugreifen.
Indem wir es so machen, können wir die Maschine in namespace library
setzen. foo
kann über ADL gefunden werden, und wenn Sie zum Beispiel mit dem Hinzufügen von Operatoren zu Ihrem Ausdruckschablonen-ähnlichen Code beginnen, müssen Sie sie nicht manuell importieren, damit Ihr main
funktioniert.
Ihre derived
ist eine Klasse, die vom Client-Code für Ihre Bibliothek erstellt wurde, also in einem anderen Namespace. Es überschreibt ()
nach Belieben und es "funktioniert einfach".
Ein weniger künstliches Beispiel würde foo
durch operator+
ersetzen, und die Vorteile dieses Stils werden offensichtlich. main
wird constexpr auto e = d+d;
, ohne dass using library::operator+
benötigt wird.
Die vorgenommenen Änderungen sind das Hinzufügen einer self()
-Methode zu base
für den Zugriff auf Derived
, das Entfernen einer static_cast
s in ()
und das Zurückgeben von foo
an expr<D1, D2>
anstelle von a expr<base<D1>, base<D2>>
.
Tags und Links c++ templates language-lawyer c++14 constexpr