Ist jede „normale“ Verwendung von benutzerdefinierten Literalen undefiniertes Verhalten?

8

Benutzerdefinierte Literale müssen mit einem Unterstrich beginnen.

Dies ist eine mehr oder weniger allgemein bekannte Regel, die Sie auf jeder Website mit Laienwörtern finden, die über Benutzerliterale spricht. Es ist auch eine Regel, die ich (und möglicherweise andere?) Seitdem auf der Basis von "Was für ein Blödsinn" offen ignoriert habe. Das ist natürlich absolut nicht richtig. Im strengsten Sinne verwendet dies einen reservierten Bezeichner und ruft somit undefiniertes Verhalten auf (obwohl Sie vom Compiler praktisch nicht einmal ein Achselzucken bekommen).

Als ich überlegte, ob ich diesen (meiner Meinung nach nutzlosen) Teil des Standards weiterhin bewusst ignorieren sollte oder nicht, beschloss ich, mir anzusehen, was tatsächlich geschrieben wurde. Denn Sie wissen, was macht es aus , was jeder weiß . Was zählt, ist, was im Standard geschrieben ist.

[over.literal]gibt an, dass "einige" Literal-Suffix-IDs reserviert sind, die mit verknüpfen [usrlit.suffix]. Letzteres besagt, dass alle reserviert sind, mit Ausnahme derjenigen, die mit einem Unterstrich beginnen. OK, das ist so ziemlich genau das, was wir bereits wussten, explizit geschrieben (oder besser gesagt rückwärts geschrieben).

Auch [over.literal]enthält einen Hinweis , die zu einer offensichtlich , aber beunruhigend Sache Hinweise:

Mit Ausnahme der oben beschriebenen Einschränkungen handelt es sich um normale Namespace-Scope-Funktionen und Funktionsvorlagen

Nun, sicher sind sie. Nirgends heißt es, dass dies nicht der Fall ist. Was würden Sie sonst von ihnen erwarten?

Aber warte einen Moment. [lex.name]Gibt ausdrücklich an, dass jeder Bezeichner, der mit einem Unterstrich im globalen Namespace beginnt, reserviert ist.

Ein Literaloperator befindet sich normalerweise, sofern Sie ihn nicht explizit in einen Namespace einfügen (was meiner Meinung nach niemand tut!?), Sehr stark im globalen Namespace. Der Name, der mit einem Unterstrich beginnen muss , ist also reserviert. Eine besondere Ausnahme wird nicht erwähnt. Jeder Name (mit oder ohne Unterstrich) ist also ein reservierter Name.

Wird von Ihnen tatsächlich erwartet, dass Sie benutzerdefinierte Literale in einen Namespace einfügen, da für die "normale" Verwendung (Unterstrich oder nicht) ein reservierter Name verwendet wird?

Damon
quelle
1
Ich frage mich, ob UDL-Suffixe als Bezeichner gelten.
HolyBlackCat
1
FWIW Ihr Code sollte sich in einem Namespace befinden und wenn Sie dem folgen, sind Sie sicher.
NathanOliver
@ NathanOliver-ReinstateMonica: Wie würde ich dieses Literal dann überhaupt verwenden? Nehmen wir an, ich habe, was auch immer, sagen ... _km(für Kilometer) es in den Namespace udl. Dann sieht ein Literal für 5km aus wie ... 5udl::_km?
Damon
@ NathanOliver-ReinstateMonica Das habe ich mir gedacht ... aber das stimmt nicht, siehe meine Antwort.
Konrad Rudolph
1
@Damon Dafür sind usingAussagen da. In dem Bereich, in dem Sie das Literal verwenden müssen, haben Sie eine using-Anweisung, die es importiert.
NathanOliver

Antworten:

6

Ja: Die Kombination aus dem Verbot der Verwendung _als Start eines globalen Bezeichners und der Anforderung, dass nicht standardmäßige UDLs beginnen müssen, _bedeutet, dass Sie sie nicht in den globalen Namespace einfügen können. Aber Sie sollten den globalen Namespace nicht mit Dingen beschmutzen, insbesondere mit UDLs, damit dies kein großes Problem darstellt.

Die traditionelle Redewendung, wie sie vom Standard verwendet wird, besteht darin, UDLs in einen literalsNamespace einzufügen (und wenn Sie unterschiedliche Sätze von UDLs haben, fügen Sie sie in einen anderen inline namespacesunter diesem Namespace ein). Dieser literalsNamespace befindet sich normalerweise unter Ihrem Haupt-Namespace. Wenn Sie einen bestimmten Satz von UDLs verwenden möchten, rufen Sie einen beliebigen using namespace my_namespace::literalsSub-Namespace auf oder einen beliebigen Sub-Namespace, der Ihre Literal-Auswahl enthält.

Dies ist wichtig, da UDLs in der Regel stark abgekürzt werden. Der Standard verwendet zum Beispiel sfür std::string, aber auch für std::chrono::durationSekunden. Während sie auf verschiedene Arten von Literalen sangewendet werden ( auf eine Zeichenfolge angewendet wird eine Zeichenfolge, während sauf eine Zahl eine Dauer angewendet wird), kann es manchmal verwirrend sein, Code zu lesen, der abgekürzte Literale verwendet. Sie sollten also nicht alle Benutzer Ihrer Bibliothek mit Literalen belegen. Sie sollten sich für die Verwendung entscheiden.

Durch die Verwendung unterschiedlicher Namespaces für diese ( std::literals::string_literalsund std::literals::chrono_literals) kann der Benutzer im Voraus festlegen, welche Literalsätze in welchen Codeteilen gewünscht werden.

Nicol Bolas
quelle
1
Leider scheint diese Antwort nicht die Gültigkeit von _FooSuffixen zu betreffen, die in der Frage weggelassen wurden, aber eher problematisch sind.
Konrad Rudolph
3

Dies ist eine gute Frage, und ich bin mir über die Antwort nicht sicher, aber ich denke, die Antwort lautet "Nein, es ist nicht UB", basierend auf einer bestimmten Lesart des Standards.

[lex.name] /3.2 lautet:

Jeder Bezeichner, der mit einem Unterstrich beginnt, ist der Implementierung zur Verwendung als Name im globalen Namespace vorbehalten.

Nun sollte die Einschränkung "als Name im globalen Namespace" eindeutig für die gesamte Regel gelten, nicht nur für die Verwendung des Namens durch die Implementierung. Das heißt, seine Bedeutung ist nicht

"Jeder Bezeichner, der mit einem Unterstrich beginnt, ist der Implementierung vorbehalten, UND die Implementierung kann solche Bezeichner als Namen im globalen Namespace verwenden."

sondern

"Die Verwendung eines Bezeichners, der mit einem Unterstrich als Name im globalen Namespace beginnt, ist der Implementierung vorbehalten."

(Wenn wir an die erste Interpretation glauben würden, würde dies bedeuten, dass niemand eine Funktion deklarieren könnte my_namespace::_foo, die zum Beispiel aufgerufen wird.)

Nach der zweiten Interpretation ist so etwas wie eine globale Deklaration von operator""_foo(im globalen Geltungsbereich) legal, da eine solche Deklaration nicht _fooals Name verwendet wird. Der Bezeichner ist vielmehr nur ein Teil des tatsächlichen Namens operator""_foo(der nicht mit einem Unterstrich beginnt).

Brian
quelle
Ich würde mit einer ähnlichen Interpretation gehen. Wenn man bedenkt, kann man eine Funktion wie definieren void operator+(foo, bar), bei der der Funktionsname eindeutig kein Bezeichner ist, sondern ein Name. Gleiches gilt operator "" _fooin unserem Fall für den Namen.
StoryTeller - Unslander Monica
2

Ist jede „normale“ Verwendung von benutzerdefinierten Literalen undefiniertes Verhalten?

Ganz sicher nicht.

Das Folgende ist die idiomatische (und damit definitiv „normale“) Verwendung von UDLs, und sie ist gemäß der soeben aufgeführten Regel gut definiert:

namespace si {
    struct metre {  };

    constexpr metre operator ""_m(long double value) { return metre{value}; }
}

Sie haben problematische Fälle aufgelistet, und ich stimme Ihrer Einschätzung hinsichtlich ihrer Gültigkeit zu, aber sie lassen sich im idiomatischen C ++ - Code leicht vermeiden, sodass ich das Problem mit dem aktuellen Wortlaut nicht vollständig sehe, selbst wenn es möglicherweise zufällig war.

Nach dem Beispiel in [over.literal] / 8 können wir nach dem Unterstrich sogar Großbuchstaben verwenden:

float operator ""E(const char*);    // error: reserved literal suffix (20.5.4.3.5, 5.13.8)
double operator""_Bq(long double);  // OK: does not use the reserved identifier _Bq (5.10)
double operator"" _Bq(long double); // uses the reserved identifier _Bq (5.10)

Das einzig problematische scheint daher die Tatsache zu sein, dass der Standard das Leerzeichen zwischen ""und den UDL-Namen signifikant macht.

Konrad Rudolph
quelle
Guter Fang, ich habe nicht einmal an Großbuchstaben gedacht.
Damon
1
Der Standard enthält ein Beispiel, das zeigt, dass dies operator""_Bqin Ordnung ist (was bedeutet, dass operator""_Kes auch in Ordnung ist). Der Trick besteht darin, das Leerzeichen zwischen ""und das Suffix wegzulassen . Siehe C ++ 17 [over.literal] / 8.
Brian
@ Brian Ich hasse es, wenn Beispiele die einzige Quelle für normative Formulierungen sind. Toller Fund. Und die Tatsache, dass sie beschlossen haben, dieses Leerzeichen bedeutend zu machen, ist noch schlimmer. In jedem Fall habe ich meine Antwort korrigiert.
Konrad Rudolph
Es ist nicht so, dass das Beispiel die einzige Quelle ist. Dies folgt vielmehr aus der Tatsache, dass ein benutzerdefiniertes Zeichenfolgenliteral ein einzelnes Vorverarbeitungstoken ist. Davon abgesehen verdeutlicht das Beispiel die Absicht, und ich hätte dies ohne das Beispiel nie realisiert.
Brian
1

Ja, das Definieren eines eigenen benutzerdefinierten Literals im globalen Namespace führt zu einem schlecht geformten Programm.

Ich bin selbst nicht darauf gestoßen, weil ich versuche, die Regel zu befolgen:

Fügen Sie nichts (außer mainNamespaces und extern "C"Sachen für die ABI-Stabilität) in den globalen Namespace ein.

namespace Mine {
  struct meter { double value; };
  inline namespace literals {
    meter operator ""_m( double v ) { return {v}; }
  }
}

int main() {
  using namespace Mine::literals;
  std::cout << 15_m.value << "\n";
}

Dies bedeutet auch, dass Sie nicht _CAPSals Literalname verwenden können, auch nicht in einem Namespace.

Die aufgerufenen Inline-Namespaces literalssind eine großartige Möglichkeit, Ihre benutzerdefinierten Literaloperatoren zu verpacken. Sie können dort importiert werden, wo Sie sie verwenden möchten, ohne genau angeben zu müssen, welche Literale Sie möchten, oder wenn Sie den gesamten Namespace importieren, erhalten Sie auch die Literale.

Dies folgt, wie die stdBibliothek auch mit Literalen umgeht, sodass Benutzer Ihres Codes damit vertraut sein sollten.

Yakk - Adam Nevraumont
quelle
0

In Anbetracht der wörtlichen mit Suffix _X, ruft die Grammatik _Xeine „Kennung“ .

Also ja: Der Standard hat es vermutlich versehentlich unmöglich gemacht, ein UDT im globalen Geltungsbereich oder UDTs, die mit einem Großbuchstaben beginnen, in einem genau definierten Programm zu erstellen. (Beachten Sie, dass Ersteres sowieso nichts ist, was Sie normalerweise tun möchten!)

Dies kann nicht redaktionell gelöst werden: Die Namen von benutzerdefinierten Literalen müssten einen eigenen lexikalischen "Namespace" haben, der Konflikte mit (zum Beispiel) Namen von von der Implementierung bereitgestellten Funktionen verhindert. Meiner Meinung nach wäre es jedoch schön gewesen, wenn es irgendwo eine nicht normative Notiz gegeben hätte, in der auf die Konsequenzen dieser Regeln hingewiesen und darauf hingewiesen wird, dass sie absichtlich sind.

Leichtigkeitsrennen im Orbit
quelle
int _x(int);Verursacht eine (global) definierte Implementierung auch mit einer Ausnahme keinen Kompilierungsfehler?
Richard Critten
Potenziell interessante weiterführende Literatur: CWG 1882
Leichtigkeitsrennen im Orbit
1
@RichardCritten Was meinst du?
Leichtigkeitsrennen im Orbit
Ich habe verstanden: "... sollte wohl eine Ausnahme haben ...", um anzuzeigen, dass benutzerdefinierte Operatoren des Formulars von _xausgenommen sein sollten [lex.name]. Wenn ich falsch verstanden habe, folgt als nächstes Müll . Wenn die Implementierung bereits eine Funktion int _x(int);im globalen Bereich deklariert hätte (reservierter Name also in Ordnung), würde ein deklarierter Benutzer long double operator "" _x(long double);(zum Beispiel) einen Kompilierungsfehler erhalten.
Richard Critten
Ich sehe nicht ein, wie eine Ausnahme dieses Problem heilen kann.
Richard Critten