constexpr und CRTP: Compiler-Uneinigkeit

8

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:

%Vor%

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%     
void-pointer 13.01.2015, 01:23
quelle

3 Antworten

9

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

%Vor%

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 cv2 D " ein, wobei D eine Klasse ist          abgeleitet (Klausel 10) von B [..]. Wenn das Objekt vom Typ " cv1 B" tatsächlich ein Unterobjekt eines Objekts vom Typ D ist, wird auf das Ergebnis verwiesen   zum einschließenden Objekt vom Typ D . 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 von e 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:

%Vor%

Und der Code funktioniert gut .

    
Columbo 13.01.2015, 02:04
quelle
5

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.

    
Tavian Barnes 13.01.2015 01:48
quelle
2

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

    
Yakk 13.01.2015 02:15
quelle