it-swarm.com.de

Welche neuen Funktionen fügen benutzerdefinierte Literale zu C ++ hinzu?

C++ 11 führt benutzerdefinierte Literale ein, wodurch die Einführung einer neuen Literal-Syntax ermöglicht wird, die auf vorhandenen Literalen (int, hex basiert. , string, float), damit jeder Typ eine wörtliche Darstellung haben kann.

Beispiele:

// imaginary numbers
std::complex<long double> operator "" _i(long double d) // cooked form
{ 
    return std::complex<long double>(0, d); 
}
auto val = 3.14_i; // val = complex<long double>(0, 3.14)

// binary values
int operator "" _B(const char*); // raw form
int answer = 101010_B; // answer = 42

// std::string
std::string operator "" _s(const char* str, size_t /*length*/) 
{ 
    return std::string(str); 
}

auto hi = "hello"_s + " world"; // + works, "hello"_s is a string not a pointer

// units
assert(1_kg == 2.2_lb); // give or take 0.00462262 pounds

Auf den ersten Blick sieht das sehr cool aus, aber ich frage mich, wie zutreffend das ist, als ich versuchte, die Suffixe _AD Und _BC Zu erstellen, fand ich, dass es aufgrund des Operators problematisch ist bestellen. 1974/01/06_AD Würde zuerst 1974/01 (Als einfaches ints) und erst später das 06_AD (Ganz zu schweigen von August und September) auswerten, ohne dass das 0 Aus oktalen Gründen). Dies kann umgangen werden, indem die Syntax 1974-1/6_AD Lautet, sodass die Operator-Auswertungsreihenfolge funktioniert, sie jedoch schwerfällig ist.

Meine Frage lautet also: Glauben Sie, dass diese Funktion sich rechtfertigen wird? Welche anderen Literale möchten Sie definieren, die Ihren C++ - Code lesbarer machen?


Die Syntax wurde aktualisiert und an den endgültigen Entwurf vom Juni 2011 angepasst

137
Motti

Hier ist ein Fall, in dem es von Vorteil ist, benutzerdefinierte Literale anstelle eines Konstruktoraufrufs zu verwenden:

#include <bitset>
#include <iostream>

template<char... Bits>
  struct checkbits
  {
    static const bool valid = false;
  };

template<char High, char... Bits>
  struct checkbits<High, Bits...>
  {
    static const bool valid = (High == '0' || High == '1')
                   && checkbits<Bits...>::valid;
  };

template<char High>
  struct checkbits<High>
  {
    static const bool valid = (High == '0' || High == '1');
  };

template<char... Bits>
  inline constexpr std::bitset<sizeof...(Bits)>
  operator"" _bits() noexcept
  {
    static_assert(checkbits<Bits...>::valid, "invalid digit in binary string");
    return std::bitset<sizeof...(Bits)>((char []){Bits..., '\0'});
  }

int
main()
{
  auto bits = 0101010101010101010101010101010101010101010101010101010101010101_bits;
  std::cout << bits << std::endl;
  std::cout << "size = " << bits.size() << std::endl;
  std::cout << "count = " << bits.count() << std::endl;
  std::cout << "value = " << bits.to_ullong() << std::endl;

  //  This triggers the static_assert at compile time.
  auto badbits = 2101010101010101010101010101010101010101010101010101010101010101_bits;

  //  This throws at run time.
  std::bitset<64> badbits2("2101010101010101010101010101010101010101010101010101010101010101_bits");
}

Der Vorteil ist, dass eine Laufzeitausnahme in einen Kompilierungsfehler konvertiert wird. Sie konnten die statische Zusicherung nicht zur Bitmenge hinzufügen, um eine Zeichenfolge zu übernehmen (zumindest nicht ohne Zeichenfolgenvorlagenargumente).

69
emsr

Auf den ersten Blick scheint es sich um einfachen syntaktischen Zucker zu handeln.

Wenn wir jedoch genauer hinschauen, sehen wir, dass es mehr als syntaktischer Zucker ist, da die Optionen des C++ - Benutzers erweitert, um benutzerdefinierte Typen zu erstellen, die sich genau wie verschiedene integrierte Typen verhalten. In dieser Hinsicht ist dieser kleine "Bonus" eine sehr interessante C++ 11-Ergänzung zu C++.

Brauchen wir es wirklich in C++?

Ich sehe nur wenige Verwendungen in dem Code, den ich in den letzten Jahren geschrieben habe, aber nur, weil ich ihn nicht in C++ verwendet habe, heißt das nicht, dass er für einen anderen C++ - Entwickler nicht interessant ist.

Wir hatten in C++ (und in C, glaube ich) vom Compiler definierte Literale verwendet, um Ganzzahlen als kurze oder lange Ganzzahlen, reelle Zahlen als Float oder Double (oder sogar Long Double) und Zeichenfolgen als normale oder breite Zeichen einzugeben .

In C++ hatten wir die Möglichkeit, unsere eigenen Typen (d. H. Klassen) zu erstellen, möglicherweise ohne Overhead (Inlining usw.). Wir hatten die Möglichkeit, Operatoren zu ihren Typen hinzuzufügen, damit sie sich wie integrierte Typen verhalten. Dies ermöglicht es C++ - Entwicklern, Matrizen und komplexe Zahlen so natürlich zu verwenden, als wären sie der Sprache selbst hinzugefügt worden. Wir können sogar Besetzungsoperatoren hinzufügen (was normalerweise eine schlechte Idee ist, aber manchmal ist es genau die richtige Lösung).

Wir haben immer noch eine Sache übersehen, bei der sich Benutzertypen wie eingebaute Typen verhalten: benutzerdefinierte Literale.

Also, ich denke, es ist eine natürliche Entwicklung für die Sprache, aber um so vollständig wie möglich zu sein: " Wenn Sie einen Typ erstellen möchten, und Sie möchten, dass er sich so gut wie ein eingebauter Typ verhält, hier sind die Werkzeuge ... "

Ich denke, es ist sehr ähnlich zu .NETs Entscheidung, jedes Primitiv zu einer Struktur zu machen, einschließlich Boolescher Werte, Ganzzahlen usw., und alle Strukturen von Object abzuleiten. Diese Entscheidung allein führt dazu, dass .NET bei der Arbeit mit Grundelementen weit außerhalb der Reichweite von Java liegt, unabhängig davon, wie viel Boxing/Unboxing-Hacks Java zu seiner Spezifikation hinzufügen.

Brauchen SIE es wirklich in C++?

Diese Frage ist für [~ # ~] Sie [~ # ~] zu beantworten. Nicht Bjarne Stroustrup. Nicht Herb Sutter. Nicht irgendein Mitglied des C++ - Standardkomitees. Aus diesem Grund haben Sie in C++ die Wahl , und sie beschränken eine nützliche Notation nicht nur auf eingebaute Typen.

Wenn Sie es brauchen, dann ist es eine willkommene Ergänzung. Wenn Sie nicht, gut ... Verwenden Sie es nicht. Es kostet dich nichts.

Willkommen bei C++, der Sprache, in der Funktionen optional sind.

Aufgebläht??? Zeig mir deine Komplexe !!!

Es gibt einen Unterschied zwischen aufgebläht und komplex (Wortspiel beabsichtigt).

Wie Niels unter Welche neuen Funktionen ergänzen benutzerdefinierte Literale C++? gezeigt hat, ist das Schreiben einer komplexen Zahl eine der beiden Funktionen, die C und C++ "kürzlich" hinzugefügt wurden:

// C89:
MyComplex z1 = { 1, 2 } ;

// C99: You'll note I is a macro, which can lead
// to very interesting situations...
double complex z1 = 1 + 2*I;

// C++:
std::complex<double> z1(1, 2) ;

// C++11: You'll note that "i" won't ever bother
// you elsewhere
std::complex<double> z1 = 1 + 2_i ;

Jetzt können sowohl der C99-Typ "double complex" als auch der C++ - Typ "std :: complex" mithilfe der Operatorüberladung multipliziert, addiert, subtrahiert usw. werden.

In C99 wurde jedoch nur ein anderer Typ als integrierter Typ hinzugefügt, und die Unterstützung für das Überladen von Operatoren wurde integriert. Und sie fügten eine weitere integrierte Literalfunktion hinzu.

In C++ haben sie nur vorhandene Merkmale der Sprache verwendet, festgestellt, dass das wörtliche Merkmal eine natürliche Weiterentwicklung der Sprache ist, und haben es daher hinzugefügt.

Wenn Sie in C dieselbe Notationserweiterung für einen anderen Typ benötigen, haben Sie bis zu Ihrer Lobbyarbeit kein Glück, Ihre Quantenwellenfunktionen (oder 3D-Punkte oder welchen Grundtyp Sie auch immer in Ihrem Arbeitsbereich verwenden) zum hinzuzufügen C-Standard als eingebauter Typ ist erfolgreich.

In C++ 11 können Sie es einfach selbst tun:

Point p = 25_x + 13_y + 3_z ; // 3D point

Ist es aufgebläht? Nein , das Bedürfnis ist da, wie gezeigt wird, indem sowohl C- als auch C++ - Komplexe eine Möglichkeit benötigen, ihre wörtlichen komplexen Werte darzustellen.

Ist es falsch konstruiert? Nein , es wurde wie jedes andere C++ - Feature unter Berücksichtigung der Erweiterbarkeit entwickelt.

Nur für Notationszwecke? Nein , da dies Ihrem Code sogar eine zusätzliche Sicherheit verleihen kann.

Stellen wir uns zum Beispiel einen CSS-orientierten Code vor:

css::Font::Size p0 = 12_pt ;       // Ok
css::Font::Size p1 = 50_percent ;  // Ok
css::Font::Size p2 = 15_px ;       // Ok
css::Font::Size p3 = 10_em ;       // Ok
css::Font::Size p4 = 15 ;         // ERROR : Won't compile !

Es ist dann sehr einfach, eine starke Typisierung der Zuweisung von Werten zu erzwingen.

Ist das gefährlich?

Gute Frage. Können diese Funktionen mit einem Namensraum versehen werden? Wenn ja, dann Jackpot!

Jedenfalls können Sie sich wie alles selbst töten, wenn ein Werkzeug unsachgemäß verwendet wird . C ist mächtig und du kannst dir den Kopf abschießen, wenn du die C-Pistole missbrauchst. In C++ gibt es die C-Waffe, aber auch das Skalpell, den Taser und alle anderen Tools, die Sie im Toolkit finden. Sie können das Skalpell missbrauchen und sich selbst verbluten. Oder Sie können sehr eleganten und robusten Code erstellen.

Brauchen Sie es also, wie jedes C++ - Feature, wirklich? Dies ist die Frage, die Sie beantworten müssen, bevor Sie sie in C++ verwenden können. Wenn Sie dies nicht tun, wird es Sie nichts kosten. Aber wenn Sie es wirklich brauchen, wird Sie die Sprache nicht im Stich lassen.

Das Datum Beispiel?

Ihr Fehler scheint mir zu sein, dass Sie Operatoren mischen:

1974/01/06AD
    ^  ^  ^

Dies kann nicht vermieden werden, da der Compiler als Operator dies interpretieren muss. Und, AFAIK, es ist eine gute Sache.

Um eine Lösung für Ihr Problem zu finden, würde ich das Literal auf eine andere Weise schreiben. Beispielsweise:

"1974-01-06"_AD ;   // ISO-like notation
"06/01/1974"_AD ;   // french-date-like notation
"jan 06 1974"_AD ;  // US-date-like notation
19740106_AD ;       // integer-date-like notation

Persönlich würde ich die Ganzzahl und das ISO-Datum wählen, aber es hängt von IHREN Bedürfnissen ab. Umso wichtiger ist es, den Benutzer seine eigenen wörtlichen Namen definieren zu lassen.

190
paercebal

Es ist sehr schön für mathematischen Code. Aus meiner Sicht kann ich die Verwendung für die folgenden Operatoren sehen:

grad für Grade. Das macht das Schreiben von absoluten Winkeln viel intuitiver.

double operator ""_deg(long double d)
{ 
    // returns radians
    return d*M_PI/180; 
}

Es kann auch für verschiedene Festkommadarstellungen verwendet werden (die im Bereich DSP und Grafik noch verwendet werden).

int operator ""_fix(long double d)
{ 
    // returns d as a 1.15.16 fixed point number
    return (int)(d*65536.0f); 
}

Diese sehen aus wie nette Beispiele, wie man es benutzt. Sie helfen dabei, Konstanten im Code besser lesbar zu machen. Es ist ein weiteres Tool, um Code ebenfalls unlesbar zu machen, aber wir haben bereits so viele Tools, die missbrauchen, dass ein weiteres nicht viel schadet.

36

UDLs haben einen Namespace (und können mithilfe von Deklarationen/Direktiven importiert werden. Sie können jedoch keinen expliziten Namespace für ein Literal wie 3.14std::i), was bedeutet, dass es (hoffentlich) keine Unmengen von Zusammenstößen gibt.

Die Tatsache, dass sie tatsächlich als Templat (und als Konstantexemplar) verwendet werden können, bedeutet, dass Sie mit UDLs einige ziemlich leistungsstarke Funktionen ausführen können. Bigint-Autoren werden sich sehr freuen, da sie endlich beliebig große Konstanten haben können, die zur Kompilierungszeit berechnet werden (über constexpr oder Templates).

Ich bin nur traurig, dass wir nicht ein paar nützliche Literale im Standard sehen (so wie es aussieht), wie s für std::string und i für die imaginäre Einheit.

Die Menge an Codierungszeit, die durch UDLs eingespart wird, ist tatsächlich nicht so hoch, aber die Lesbarkeit wird erheblich erhöht und immer mehr Berechnungen können zur schnelleren Ausführung in die Kompilierungszeit verschoben werden.

17
coppro

Lassen Sie mich ein wenig Kontext hinzufügen. Für unsere Arbeit werden benutzerdefinierte Literale benötigt. Wir arbeiten an MDE (Model-Driven Engineering). Wir wollen Modelle und Metamodelle in C++ definieren. Wir haben tatsächlich ein Mapping von Ecore nach C++ implementiert ( EMF4CPP ).

Das Problem tritt auf, wenn Modellelemente in C++ als Klassen definiert werden können. Wir verfolgen den Ansatz, das Metamodell (Ecore) in Vorlagen mit Argumenten umzuwandeln. Argumente der Vorlage sind die strukturellen Merkmale von Typen und Klassen. Zum Beispiel würde eine Klasse mit zwei int-Attributen ungefähr so ​​aussehen:

typedef ::ecore::Class< Attribute<int>, Attribute<int> > MyClass;

Es stellt sich jedoch heraus, dass jedes Element in einem Modell oder Metamodell normalerweise einen Namen hat. Wir möchten schreiben:

typedef ::ecore::Class< "MyClass", Attribute< "x", int>, Attribute<"y", int> > MyClass;

ABER, C++ oder C++ 0x erlauben dies nicht, da Zeichenfolgen als Argumente für Vorlagen verboten sind. Sie können den Namen char von char schreiben, aber das ist zugegebenermaßen ein Durcheinander. Mit geeigneten benutzerdefinierten Literalen könnten wir etwas Ähnliches schreiben. Angenommen, wir verwenden "_n", um Modellelementnamen zu identifizieren (ich verwende nicht die exakte Syntax, nur um eine Idee zu haben):

typedef ::ecore::Class< MyClass_n, Attribute< x_n, int>, Attribute<y_n, int> > MyClass;

Schließlich hilft es uns sehr, diese Definitionen als Vorlagen zu haben, um Algorithmen zum Durchlaufen der Modellelemente, Modelltransformationen usw. zu entwerfen, die wirklich effizient sind, da Typinformationen, Identifikation, Transformationen usw. vom Compiler zur Kompilierzeit bestimmt werden.

12
Diego Sevilla

Bjarne Stroustrup spricht über UDLs in diesem C++ 11-Vortrag , im ersten Abschnitt über typreiche Schnittstellen, ungefähr 20 Minuten.

Sein grundlegendes Argument für UDLs ist ein Syllogismus:

  1. "Triviale" Typen, d. H. Eingebaute primitive Typen, können nur triviale Typfehler auffangen. Durch Schnittstellen mit umfangreicheren Typen kann das Typsystem mehr Arten von Fehlern erfassen.

  2. Die Arten von Tippfehlern, die durch reichhaltigen Code aufgefangen werden können, wirken sich auf den tatsächlichen Code aus. (Er gibt das Beispiel des Mars Climate Orbiter an, der bekanntermaßen aufgrund eines Dimensionsfehlers in einer wichtigen Konstante versagt hat).

  3. In echtem Code werden Einheiten selten verwendet. Die Benutzer verwenden sie nicht, da die Erstellung umfangreicher Typen über die Laufzeit berechnet oder übermäßig viel Speicherplatz beansprucht wird und die Verwendung von vorbestehendem Unit-Code mit C++-Vorlage so fälschlicherweise hässlich ist, dass niemand darauf zurückgreift. (Empirisch benutzt es niemand, obwohl es die Bibliotheken schon seit einem Jahrzehnt gibt).

  4. Um Ingenieure dazu zu bringen, Einheiten in realem Code zu verwenden, benötigten wir daher ein Gerät, das (1) keinen Laufzeit-Overhead verursacht und (2) notational akzeptabel ist.

10
masonk

Die Unterstützung der Dimensionsprüfung zur Kompilierungszeit ist die einzige erforderliche Begründung.

auto force = 2_N; 
auto dx = 2_m; 
auto energy = force * dx; 

assert(energy == 4_J); 

Siehe zum Beispiel PhysUnits-CT-Cpp11 , eine kleine C++ 11, C++ 14-Bibliothek nur für Header zur Dimensionsanalyse zur Kompilierungszeit sowie zur Manipulation und Konvertierung von Einheiten und Mengen. Einfacher als Boost.Units , unterstützt Einheitensymbol Literale wie m, g, s, metrische Präfixe wie m, k, M, hängt nur von der Standard-C++ - Bibliothek ab, nur SI, ganzzahlige Potenzen von Dimensionen.

8
Martin Moene

Hmm ... über diese Funktion habe ich noch nicht nachgedacht. Ihre Probe war gut durchdacht und ist sicherlich interessant. C++ ist so mächtig wie es jetzt ist, aber leider ist die Syntax, die in den von Ihnen gelesenen Codeteilen verwendet wird, manchmal zu komplex. Lesbarkeit ist, wenn nicht alles, dann zumindest viel. Und ein solches Feature wäre auf mehr Lesbarkeit ausgerichtet. Wenn ich dein letztes Beispiel nehme

assert(1_kg == 2.2_lb); // give or take 0.00462262 pounds

... Ich frage mich, wie Sie das heute ausdrücken würden. Sie hätten eine KG- und eine LB-Klasse und würden implizite Objekte vergleichen:

assert(KG(1.0f) == LB(2.2f));

Und das würde auch reichen. Bei Typen mit längeren Namen oder Typen, von denen Sie nicht hoffen können, dass sie einen solchen Nice-Konstruktor für das Schreiben eines Adapters haben, ist dies möglicherweise eine nette Ergänzung für die implizite Objekterstellung und -initialisierung im laufenden Betrieb. Andererseits können Sie Objekte auch bereits mit Methoden erstellen und initialisieren.

Aber ich stimme mit Nils in Mathematik überein. C- und C++ - Trigonometriefunktionen erfordern beispielsweise die Eingabe im Bogenmaß. Ich denke aber graduell, also ist eine sehr kurze implizite Konvertierung wie Nils sehr schön.

Letztendlich wird es sich jedoch um syntaktischen Zucker handeln, der sich jedoch geringfügig auf die Lesbarkeit auswirkt. Und es wird wahrscheinlich auch einfacher sein, einige Ausdrücke zu schreiben (sin (180.0deg) ist einfacher zu schreiben als sin (deg (180.0)). Und dann wird es Leute geben, die das Konzept missbrauchen. Aber dann sollten Leute, die die Sprache missbrauchen, es benutzen sehr restriktive Sprachen und nicht so ausdrucksstark wie C++.

Ah, in meinem Post steht im Grunde nichts anderes als: Es wird in Ordnung sein, die Wirkung wird nicht zu groß sein. Machen wir uns keine Sorgen. :-)

6
mstrobl

Ich habe dieses Feature noch nie gebraucht oder gewollt (aber das könnte der Blub Effekt sein). Meine Reaktion ist, dass es lahm ist und wahrscheinlich die gleichen Leute anspricht, die denken, dass es cool ist, den Bediener für jede Operation zu überlasten, die aus der Ferne als Hinzufügen ausgelegt werden könnte.

3
fizzer

C++ ist in der Regel sehr streng in Bezug auf die verwendete Syntax - abgesehen vom Präprozessor können Sie nicht viel verwenden, um eine benutzerdefinierte Syntax/Grammatik zu definieren. Z.B. Wir können existierende Opern überladen, aber keine neuen definieren - IMO, dies ist sehr im Einklang mit dem Geist von C++.

Ich habe nichts dagegen, wenn es um individuelleren Quellcode geht - aber der gewählte Punkt scheint mir sehr isoliert zu sein, was mich am meisten verwirrt.

Selbst bei bestimmungsgemäßer Verwendung kann das Lesen von Quellcode erheblich erschwert werden: Ein einzelner Buchstabe kann weitreichende Nebenwirkungen haben, die im Zusammenhang in keiner Weise zu erkennen sind. Mit der Symmetrie zu u, l und f wählen die meisten Entwickler einzelne Buchstaben.

Dies kann auch dazu führen, dass das Scoping zu einem Problem wird. Die Verwendung einzelner Buchstaben im globalen Namespace wird wahrscheinlich als schlechte Praxis angesehen, und die Tools, die das Mischen von Bibliotheken erleichtern sollen (Namespaces und beschreibende Bezeichner), werden wahrscheinlich ihren Zweck verfehlen.

Ich sehe einige Vorteile in Kombination mit "auto", auch in Kombination mit einer Einheitenbibliothek wie Boost Units , aber nicht genug, um diese Addition zu verdienen.

Ich frage mich jedoch, welche cleveren Ideen uns einfallen.

2
peterchen

Ich habe Benutzerliterale für binäre Zeichenfolgen wie diese verwendet:

 "asd\0\0\0\1"_b

verwenden Sie den Konstruktor std::string(str, n), damit \0 die Zeichenfolge nicht halbiert. (Das Projekt arbeitet viel mit verschiedenen Dateiformaten.)

Dies war auch hilfreich, als ich std::string Zugunsten eines Wrappers für std::vector Abgeworfen habe.

2
rr-