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?
quelle
_km
(für Kilometer) es in den Namespaceudl
. Dann sieht ein Literal für 5km aus wie ...5udl::_km
?using
Aussagen da. In dem Bereich, in dem Sie das Literal verwenden müssen, haben Sie eine using-Anweisung, die es importiert.Antworten:
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
literals
Namespace einzufügen (und wenn Sie unterschiedliche Sätze von UDLs haben, fügen Sie sie in einen andereninline namespaces
unter diesem Namespace ein). Dieserliterals
Namespace befindet sich normalerweise unter Ihrem Haupt-Namespace. Wenn Sie einen bestimmten Satz von UDLs verwenden möchten, rufen Sie einen beliebigenusing namespace my_namespace::literals
Sub-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
s
fürstd::string
, aber auch fürstd::chrono::duration
Sekunden. Während sie auf verschiedene Arten von Literalens
angewendet werden ( auf eine Zeichenfolge angewendet wird eine Zeichenfolge, währends
auf 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_literals
undstd::literals::chrono_literals
) kann der Benutzer im Voraus festlegen, welche Literalsätze in welchen Codeteilen gewünscht werden.quelle
_Foo
Suffixen zu betreffen, die in der Frage weggelassen wurden, aber eher problematisch sind.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:
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_foo
als Name verwendet wird. Der Bezeichner ist vielmehr nur ein Teil des tatsächlichen Namensoperator""_foo
(der nicht mit einem Unterstrich beginnt).quelle
void operator+(foo, bar)
, bei der der Funktionsname eindeutig kein Bezeichner ist, sondern ein Name. Gleiches giltoperator "" _foo
in unserem Fall für den Namen.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:
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:
Das einzig problematische scheint daher die Tatsache zu sein, dass der Standard das Leerzeichen zwischen
""
und den UDL-Namen signifikant macht.quelle
operator""_Bq
in Ordnung ist (was bedeutet, dassoperator""_K
es auch in Ordnung ist). Der Trick besteht darin, das Leerzeichen zwischen""
und das Suffix wegzulassen . Siehe C ++ 17 [over.literal] / 8.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
main
Namespaces undextern "C"
Sachen für die ABI-Stabilität) in den globalen Namespace ein.Dies bedeutet auch, dass Sie nicht
_CAPS
als Literalname verwenden können, auch nicht in einem Namespace.Die aufgerufenen Inline-Namespaces
literals
sind 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
std
Bibliothek auch mit Literalen umgeht, sodass Benutzer Ihres Codes damit vertraut sein sollten.quelle
In Anbetracht der wörtlichen mit Suffix
_X
, ruft die Grammatik_X
eine „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.
quelle
int _x(int);
Verursacht eine (global) definierte Implementierung auch mit einer Ausnahme keinen Kompilierungsfehler?_x
ausgenommen sein sollten[lex.name]
. Wenn ich falsch verstanden habe, folgt als nächstes Müll . Wenn die Implementierung bereits eine Funktionint _x(int);
im globalen Bereich deklariert hätte (reservierter Name also in Ordnung), würde ein deklarierter Benutzerlong double operator "" _x(long double);
(zum Beispiel) einen Kompilierungsfehler erhalten.