Eine der Ecken von C ++ 20-Konzepten ist, dass es bestimmte Situationen gibt, in denen Sie schreiben müssen requires requires
. Zum Beispiel dieses Beispiel aus [expr.prim.req] / 3 :
Ein Requires-Ausdruck kann auch in einer Requires-Klausel ([temp]) verwendet werden, um Ad-hoc-Einschränkungen für Vorlagenargumente wie das folgende zu schreiben:
template<typename T> requires requires (T x) { x + x; } T add(T a, T b) { return a + b; }
Das erste erfordert die Einführung der require-Klausel und das zweite die Einführung des require-Ausdrucks .
Was ist der technische Grund für die Notwendigkeit dieses zweiten requires
Schlüsselworts? Warum können wir nicht einfach schreiben lassen:
template<typename T>
requires (T x) { x + x; }
T add(T a, T b) { return a + b; }
(Hinweis: Bitte antworten Sie nicht auf die Grammatik requires
)
c++
c++-concepts
c++20
Barry
quelle
quelle
noexcept(noexcept(...))
.requires
sind meiner Meinung nach Homonyme: Sie sehen gleich aus, buchstabieren gleich, riechen gleich, unterscheiden sich aber grundsätzlich. Wenn ich eine Lösung vorschlagen würde, würde ich vorschlagen, eine davon umzubenennen.co_requires
? (Entschuldigung, konnte nicht widerstehen).long long
.Antworten:
Es ist, weil die Grammatik es erfordert. Es tut.
Eine
requires
Einschränkung nicht haben einen verwendenrequires
Ausdruck. Es kann jeden mehr oder weniger willkürlichen booleschen konstanten Ausdruck verwenden. Daherrequires (foo)
muss eine legitimerequires
Einschränkung sein.Ein
requires
Ausdruck (das Ding, das prüft, ob bestimmte Dinge bestimmten Einschränkungen folgen) ist ein bestimmtes Konstrukt; Es wird nur mit demselben Schlüsselwort eingeführt.requires (foo f)
wäre der Anfang eines gültigenrequires
Ausdrucks.Was Sie wollen, ist, dass Sie, wenn Sie
requires
an einem Ort verwenden, der Einschränkungen akzeptiert, in der Lage sein sollten, aus derrequires
Klausel einen "Constraint + Ausdruck" zu machen .Hier ist also die Frage: Wenn Sie an
requires (foo)
einen Ort setzen, der für eine erforderliche Einschränkung geeignet ist ... wie weit muss der Parser gehen, bevor er erkennen kann, dass dies eine erforderliche Einschränkung ist und nicht eine Einschränkung + Ausdruck, wie Sie es möchten sein?Bedenken Sie:
Wenn
foo
es sich um einen Typ handelt,(foo)
handelt es sich um eine Parameterliste eines erforderlichen Ausdrucks, und alles in{}
ist nicht der Hauptteil der Funktion, sondern der Hauptteil diesesrequires
Ausdrucks. Andernfallsfoo
ist ein Ausdruck in einerrequires
Klausel.Nun, man könnte sagen, dass der Compiler nur herausfinden sollte, was
foo
zuerst kommt. Aber C ++ mag es wirklich nicht, wenn der Compiler beim Parsen einer Folge von Token herausfinden muss, was diese Bezeichner bedeuten, bevor er einen Sinn für die Token ergibt. Ja, C ++ ist kontextsensitiv, dies geschieht also. Der Ausschuss zieht es jedoch vor, dies nach Möglichkeit zu vermeiden.Also ja, es ist Grammatik.
quelle
requires
nach einer<>
Reihe von Vorlagenargumenten oder nach einer Funktionsparameterliste angezeigt wird, handelt es sich um eine require -Klausel. Wenn dortrequires
angezeigt wird, wo ein Ausdruck gültig ist, handelt es sich um einen erforderlichen Ausdruck. Dies kann durch die Struktur des Analysebaums bestimmt werden, nicht durch den Inhalt des Analysebaums (die Besonderheiten, wie ein Bezeichner definiert wird, sind der Inhalt des Baums).concept
undrequires
. Die Einführung eines dritten, bei dem der zweite beide Fälle ohne grammatikalische Probleme und mit wenigen Problemen für den Benutzer abdecken könnte, ist nur verschwenderisch. Schließlich besteht das einzige visuelle Problem darin, dass das Schlüsselwort zufällig zweimal wiederholt wird.Die Situation ist genau analog zu
noexcept(noexcept(...))
. Sicher, das klingt eher nach einer schlechten als nach einer guten Sache, aber lassen Sie mich das erklären. :) Wir beginnen mit dem, was Sie bereits wissen:C ++ 11 hat "
noexcept
-clauses" und "-expressions"noexcept
. Sie machen verschiedene Dinge.Eine
noexcept
Klausel sagt: "Diese Funktion sollte nicht außer ... (eine Bedingung) sein." Es geht auf eine Funktionsdeklaration, nimmt einen booleschen Parameter und verursacht eine Verhaltensänderung in der deklarierten Funktion.Ein Ausdruck
noexcept
sagt: "Compiler, bitte sagen Sie mir, ob (irgendein Ausdruck) keine Ausnahme ist." Es ist selbst ein boolescher Ausdruck. Es hat keine "Nebenwirkungen" auf das Verhalten des Programms - es fragt nur den Compiler nach der Antwort auf eine Ja / Nein-Frage. "Ist dieser Ausdruck keine Ausnahme?"Wir können Nest einen
noexcept
-Ausdrucks in einemnoexcept
-Klausel, aber wir es in der Regel schlechten Stil betrachten , dies zu tun.Es wird als besserer Stil angesehen, den
noexcept
Ausdruck in ein Typmerkmal zu kapseln .Der C ++ 2a Working Draft enthält "
requires
-clauses" und "-expressions"requires
. Sie machen verschiedene Dinge.Eine
requires
Klausel besagt: "Diese Funktion sollte an der Überlastungsauflösung teilnehmen, wenn ... (eine Bedingung)." Es geht auf eine Funktionsdeklaration, nimmt einen booleschen Parameter und verursacht eine Verhaltensänderung in der deklarierten Funktion.Ein
requires
Ausdruck sagt: "Compiler, bitte sagen Sie mir, ob (einige Ausdrücke) wohlgeformt sind." Es ist selbst ein boolescher Ausdruck. Es hat keine "Nebenwirkungen" auf das Verhalten des Programms - es fragt nur den Compiler nach der Antwort auf eine Ja / Nein-Frage. "Ist dieser Ausdruck wohlgeformt?"Wir können Nest einen
requires
-Ausdrucks in einemrequires
-Klausel, aber wir es in der Regel schlechten Stil betrachten , dies zu tun.Es wird als besserer Stil angesehen, den
requires
Ausdruck in ein Typmerkmal zu kapseln ...... oder in einem (C ++ 2a Working Draft) Konzept.
quelle
noexcept
hat das Problem , dassnoexcept(f())
könnte bedeuten , entweder interpretierenf()
als boolean , dass wir die Spezifikation festgelegt verwenden oder zu prüfen , ob oder nichtf()
istnoexcept
.requires
hat diese Mehrdeutigkeit nicht, da die Ausdrücke, deren Gültigkeit überprüft wird, bereits mit{}
s eingeführt werden müssen. Danach lautet das Argument im Grunde "die Grammatik sagt es".{}
optional sind.{}
sind nicht optional, das zeigt dieser Kommentar nicht. Dies ist jedoch ein großartiger Kommentar, der die Mehrdeutigkeit beim Parsen demonstriert. Würde diesen Kommentar (mit einigen Erklärungen) wahrscheinlich als eigenständige Antwort akzeptierenrequires is_nothrow_incrable_v<T>;
sollte seinrequires is_incrable_v<T>;
Ich denke, die Konzeptseite von cppreference erklärt dies. Ich kann sozusagen mit "Mathe" erklären, warum das so sein muss:
Wenn Sie ein Konzept definieren möchten, gehen Sie folgendermaßen vor:
Wenn Sie eine Funktion deklarieren möchten, die dieses Konzept verwendet, gehen Sie folgendermaßen vor:
Wenn Sie das Konzept nicht separat definieren möchten, müssen Sie wahrscheinlich nur eine Substitution durchführen. Nehmen Sie dieses Teil
requires (T x) { x + x; };
und ersetzen Sie dasAddable<T>
Teil, und Sie erhalten:Nach was fragst du?
quelle
template<typename T> requires (T x) { x + x; }
und verlangen Sie nicht , dass erfordern sowohl die Klausel als auch der Ausdruck sein kann?requires
-as-Beschränkungsklausel nicht müssen sein ,requires
-Ausdrucks. Das ist nur eine mögliche Verwendung davon.requires requires
. Sie hätten der Grammatik etwas hinzufügen können, um dies zuzulassen,template<typename T> requires (T x) { x + x; }
aber sie taten es nicht. Barry will wissen, warum sie es nicht getan habentemplate<class T> void f(T) requires requires(T (x)) { (void)x; };
bedeutet etwas anderes, wenn Sie eines derrequires
es entfernen .Ich fand einen Kommentar von Andrew Sutton (einem der Concepts-Autoren, der ihn in gcc implementiert hat) in dieser Hinsicht sehr hilfreich, daher dachte ich, ich würde ihn hier fast vollständig zitieren:
quelle
Weil Sie sagen, dass eine Sache A eine Anforderung B hat und die Anforderung B eine Anforderung C.
Das Ding A benötigt B, was wiederum C erfordert.
Die "require" -Klausel selbst erfordert etwas.
Sie haben Ding A (erfordert B (erfordert C)).
Meh. :) :)
quelle
requires
konzeptionell nicht dasselbe (eine ist eine Klausel, eine ein Ausdruck). Wenn ich das richtig verstehe, haben die beiden Sätze von()
inrequires (requires (T x) { x + x; })
sehr unterschiedliche Bedeutungen (die äußere ist optional und enthält immer einen booleschen Zusammenhang; die innere ist ein obligatorischer Bestandteil der Einführung eines erforderlichen Ausdrucks und lässt keine tatsächlichen Ausdrücke zu).