Ist der Unterschied zwischen zwei constexpr-Instanzen von __func__ -Zeigern noch constexpr?

14

Ist das gültiges C ++?

int main() {
    constexpr auto sz = __func__ - __func__;
    return sz;
}

GCC und MSVC denken, dass es in Ordnung ist, Clang denkt, dass es nicht: Compiler Explorer .


Alle Compiler sind sich einig, dass dies in Ordnung ist: Compiler Explorer .

int main() {
    constexpr auto p = __func__;
    constexpr auto p2 = p;
    constexpr auto sz = p2 - p;
    return sz;
}

Clang mag dieses wieder nicht, aber die anderen sind damit einverstanden: Compiler Explorer

int main() {
    constexpr auto p = __func__;
    constexpr auto p2 = __func__;
    constexpr auto sz = p2 - p;
    return sz;
}

Was ist hier oben? Ich denke, Arithmetik für nicht verwandte Zeiger ist undefiniertes Verhalten, __func__gibt aber den gleichen Zeiger zurück, nein? Ich bin mir nicht sicher, also dachte ich, ich könnte es testen. Wenn ich mich richtig erinnere, std::equal_tokönnen nicht verwandte Zeiger ohne undefiniertes Verhalten verglichen werden:

#include <functional>

int main() {
    constexpr std::equal_to<const char*> eq{};
    static_assert(eq(__func__, __func__));
}

Clang denkt, dass dies eq(__func__, __func__)kein konstanter Ausdruck ist, obwohl er std::equal_to::operator() constexpr ist . Andere Compiler beschweren sich nicht: Compiler Explorer


Clang wird diesen auch nicht kompilieren. Beschwert sich, dass dies __func__ == __func__kein konstanter Ausdruck ist: Compiler Explorer

int main() {
    static_assert(__func__ == __func__);
}
Ayxan
quelle
Von Function_definition , __func__ist als-ob static const char __func__[] = "function-name";und gleichwertig akzeptiert Demo ...
Jarod42
Interessanterweise funktioniert es, wenn Sie eine constexpr-Variable mit initialisieren __func__und diese im static_assert verwenden ...
florestan
@ Jarod42 Das ist also ein Fehler in Clang?
Ayxan
@florestan so ? Es wird auch nicht mit Clang kompiliert. Meine zweiten und dritten Beispiele in der Frage sind die von Ihnen erwähnten. Einer kompiliert, der andere nicht.
Ayxan
1
Siehe auch CWG1962 , das möglicherweise __func__vollständig aus der Constexpr-Bewertung entfernt wird.
Davis Herring

Antworten:

13

__func__in C ++ ist ein Bezeichner. Insbesondere verweist es auf ein bestimmtes Objekt. Aus [dcl.fct.def.general] / 8 :

Die funktionslokale vordefinierte Variable _­_­func_­_­wird als Definition des Formulars definiert

static const char __func__[] = "function-name";

wurde bereitgestellt, wobei Funktionsname eine implementierungsdefinierte Zeichenfolge ist. Es ist nicht spezifiziert, ob eine solche Variable eine Adresse hat, die sich von der eines anderen Objekts im Programm unterscheidet.

Als funktionslokale vordefinierte Variable erscheint diese Definition (als ob) am Anfang des Funktionsblocks. Daher __func__bezieht sich jede Verwendung innerhalb dieses Blocks auf diese Variable.

Für den Teil "jedes andere Objekt" definiert eine Variable ein Objekt. __func__benennt das von dieser Variablen definierte Objekt. Daher haben innerhalb einer Funktion alle Verwendungen des __func__Namens dieselbe Variable. Undefiniert ist, ob diese Variable ein anderes Objekt als andere Objekte ist.

Das heißt, wenn Sie sich in einer Funktion mit dem Namen befinden foound das Literal an einer "foo"anderen Stelle im Problem verwendet haben, ist es für eine Implementierung nicht verboten, dass die Variable __func__auch dasselbe Objekt ist, das das Literal "foo"zurückgibt. Das heißt, der Standard verlangt nicht, dass jede Funktion, in der sie __func__angezeigt wird, Daten getrennt vom Zeichenfolgenliteral selbst speichern muss.

Die "Als ob" -Regel von C ++ erlaubt es Implementierungen, davon abzuweichen, aber sie können dies nicht auf eine Weise tun, die erkennbar wäre. Während die Variable selbst möglicherweise eine andere Adresse als andere Objekte hat oder nicht, müssen sich Verwendungen __func__in derselben Funktion so verhalten, als würden sie sich auf dasselbe Objekt beziehen.

Clang scheint __func__diesen Weg nicht umzusetzen . Es scheint so zu implementieren, als hätte es ein prvalue-String-Literal des Funktionsnamens zurückgegeben. Zwei unterschiedliche Zeichenfolgenliterale müssen sich nicht auf dasselbe Objekt beziehen, daher ist das Subtrahieren von Zeigern auf sie UB. Und undefiniertes Verhalten in einem Kontext mit konstantem Ausdruck ist schlecht geformt.

Das einzige, was mich zögern lässt zu sagen, dass Clang hier zu 100% falsch ist, ist [temp.arg.nontype] / 2 :

Für einen Nicht-Typ-Vorlagenparameter vom Referenz- oder Zeigertyp darf sich der Wert des konstanten Ausdrucks nicht beziehen (oder für einen Zeigertyp nicht die Adresse von):

...

  • eine vordefinierte _­_­func_­_Variable.

Sehen Sie, dies scheint ein gewisses Durcheinander bei der Implementierung zu ermöglichen. Das heißt, obwohl __func__dies technisch gesehen ein konstanter Ausdruck sein kann, können Sie ihn nicht in einem Vorlagenparameter verwenden. Es wird wie ein String-Literal behandelt, obwohl es technisch gesehen eine Variable ist.

In gewisser Hinsicht würde ich sagen, dass der Standard von beiden Seiten seines Mundes spricht.

Nicol Bolas
quelle
Streng genommen __func__kann das also in allen Fällen meiner Frage ein ständiger Ausdruck sein, oder? Der Code sollte also kompiliert sein.
Ayxan
Was ist mit "Es ist nicht spezifiziert, ob eine solche Variable eine Adresse hat, die sich von der eines anderen Objekts im Programm unterscheidet." Teil? Nicht spezifiziertes Verhalten bedeutet Nichtdeterminismus im Verhalten der abstrakten Maschine. Kann das für die constexpr-Bewertung problematisch sein? Was ist, wenn das erste Auftreten __func__der Adresse mit dem eines anderen Objekts identisch ist und das zweite __func__nicht? Zugegeben, das bedeutet nicht, dass sich die Adresse zwischen den beiden Instanzen unterscheidet, aber ich bin immer noch verwirrt!
Johannes Schaub - Litb
@ JohannesSchaub-litb: " Was ist mit dem" Es ist nicht spezifiziert, ob eine solche Variable eine Adresse hat, die sich von der eines anderen Objekts im Programm unterscheidet. "Teil? " Was ist damit? __func__ist kein Makro; Es ist ein Bezeichner, der eine bestimmte Variable und damit ein bestimmtes Objekt benennt. Daher sollte jede Verwendung __func__in derselben Funktion zu einem Wert führen, der sich auf dasselbe Objekt bezieht. Oder genauer gesagt, es kann nicht so implementiert werden, dass dies nicht der Fall wäre.
Nicol Bolas
@Nicol, das sich auf dasselbe Objekt bezieht. Dieses Objekt kann jedoch zu einem bestimmten Zeitpunkt dieselbe Adresse wie ein anderes Objekt haben. Und im anderen Moment nicht. Ich sage nicht, dass das ein Problem ist, aber ich erinnere nur alle an diese Möglichkeit. Und schließlich kann ich mich auch irren, also sage ich dies auch in der Hoffnung, korrigiert oder bestätigt zu werden.
Johannes Schaub - Litb
@ JohannesSchaub-litb: "Dieses Objekt kann jedoch zu einem bestimmten Zeitpunkt dieselbe Adresse wie ein anderes Objekt haben. " Dies ist im C ++ - Objektmodell nicht zulässig. Zwei Objekte, von denen keines im anderen verschachtelt ist, können sich nicht beide innerhalb ihrer Lebensdauer gleichzeitig im selben Speicher befinden. Das betreffende Objekt hat eine statische Speicherdauer. Wenn Sie also keine Platzierung verwenden new, wird es erst dann ausgeführt, wenn das Programm endet.
Nicol Bolas