C ++ - Dekltyp und Klammern - warum?

32

Das Thema wurde diskutiert vor , aber dies ist kein Duplikat.

Wenn jemand nach dem Unterschied zwischen decltype(a)und fragt , lautet decltype((a))die übliche Antwort: - aist eine Variable, (a)ist ein Ausdruck. Ich finde diese Antwort unbefriedigend.

Erstens aist auch ein Ausdruck. Die Optionen für einen primären Ausdruck umfassen unter anderem:

  • ( Ausdruck )
  • ID-Ausdruck

Noch wichtiger ist, dass die Formulierung für decltype Klammern sehr, sehr explizit berücksichtigt :

For an expression e, the type denoted by decltype(e) is defined as follows:
(1.1)  if e is an unparenthesized id-expression naming a structured binding, ...
(1.2)  otherwise, if e is an unparenthesized id-expression naming a non-type template-parameter, ...
(1.3)  otherwise, if e is an unparenthesized id-expression or an unparenthesized class member access, ...
(1.4)  otherwise, ...

Die Frage bleibt also. Warum werden Klammern anders behandelt? Ist jemand mit technischen Unterlagen oder Ausschussdiskussionen dahinter vertraut? Die explizite Berücksichtigung von Klammern führt zu der Annahme, dass dies kein Versehen ist. Es muss also einen technischen Grund geben, den ich vermisse.

Ofek Shilon
quelle
2
"Die übliche Antwort ist - a ist eine Variable, (a) ist ein Ausdruck" Was sie bedeuten ist " (a)ist ein Ausdruck und aist ein Ausdruck und eine Variable".
HolyBlackCat

Antworten:

18

Es ist kein Versehen. Es ist interessant, dass es in Decltype und Auto (Revision 4) (N1705 = 04-0145) eine Aussage gibt:

Die Decltype-Regeln geben dies jetzt explizit an decltype((e)) == decltype(e)(wie von der EWG vorgeschlagen).

Aber in Decltype (Revision 6): Vorgeschlagener Wortlaut (N2115 = 06-018) ist eine der Änderungen

Der Ausdruck in Klammern innerhalb des Dekltyps wird nicht als id-expression.

Der Wortlaut enthält keine Begründung, aber ich nehme an, dass dies eine Art Erweiterung des Dekltyps unter Verwendung einer etwas anderen Syntax ist, mit anderen Worten, es war beabsichtigt, diese Fälle zu unterscheiden.

Die Verwendung dafür wird in C ++ Draft9.2.8.4 gezeigt:

const int&& foo();
int i;
struct A { double x; };
const A* a = new A();
decltype(foo()) x1 = 17;        // type is const int&&
decltype(i) x2;                 // type is int
decltype(a->x) x3;              // type is double
decltype((a->x)) x4 = x3;       // type is const double&

Was wirklich interessant ist, ist, wie es mit der returnAussage funktioniert :

decltype(auto) f()
{
    int i{ 0 };
    return (i);
}

Mein Visual Studio 2019 schlägt mir vor, redundante Klammern zu entfernen, aber tatsächlich wird daraus, decltype((i))welche Änderungen einen Rückgabewert ergeben, int&der es zu UB macht, seit der Verweis auf eine lokale Variable zurückgegeben wird.

espkk
quelle
Vielen Dank, dass Sie den Ursprung genau festgelegt haben! Decltype konnte nicht nur anders angegeben werden, es stellte sich auch heraus, dass dies ursprünglich der Fall war. Das Rev-6-Dokument fügt dieses lustige Verhalten in Klammern explizit hinzu, überspringt jedoch die Begründung :(. Ich denke, das kommt einer Antwort so nahe, wie wir sie jetzt bekommen würden.
Ofek Shilon
Und doch schlug niemand vor, das dumme Problem zu lösen, indem man es zu zwei verschiedenen Schlüsselwörtern für zwei verschiedene Funktionen machte? Einer der größten Fehler des Komitees (der historisch viele ungezwungene Fehler gemacht hat).
Neugieriger
13

Warum werden Klammern anders behandelt?

Klammern werden nicht anders behandelt. Es ist der unparenthesierte ID-Ausdruck, der anders behandelt wird.

Wenn die Klammern vorhanden sind, gelten die regulären Regeln für alle Ausdrücke. Der Typ und die Wertekategorie werden extrahiert und im Typ von codiert decltype.

Die spezielle Bestimmung ist da, damit wir nützlichen Code leichter schreiben können. Wenn wir decltypeauf den Namen einer (Mitglieds-) Variablen anwenden , möchten wir normalerweise keinen Typ, der die Eigenschaften der Variablen darstellt, wenn er als Ausdruck behandelt wird. Stattdessen möchten wir nur den Typ, mit dem die Variable deklariert wird, ohne eine Menge Typmerkmale anwenden zu müssen, um darauf zuzugreifen. Und genau das decltypeist angegeben, um uns zu geben.

Wenn wir uns um die Eigenschaften der Variablen als Ausdruck kümmern, können wir sie mit einem zusätzlichen Klammerpaar immer noch recht einfach abrufen.

Geschichtenerzähler - Unslander Monica
quelle
Also für ein intMitglied ivon a, decltype(a.i)ist intwährend decltype((a.i))ist int&(vorausgesetzt, aist nicht const)? Da der Ausdruck a.izuweisbar ist?
n314159
1
@ n314159 - Das ist der Kern davon. Der Ausdruck a.iist ein nicht konstanter Wert, daher erhalten Sie einen nicht konstanten Werttyp für (a.i).
StoryTeller - Unslander Monica
Warum geben "die regulären Regeln für alle Ausdrücke" an, dass ihr Typ eine Referenz ist? Warum enthalten 'die Eigenschaften der Variablen als Ausdruck' die Eigenschaft, eine Referenz zu sein? Ist es ein natürlicher Standard?
Ofek Shilon
1
@OfekShilon - Ihr Typ ist keine Referenz. Der Typ eines Ausdrucks wird niemals als Referenz analysiert . Dectlype kann jedoch nur in einen Typ aufgelöst werden und soll uns nicht nur den Typ eines Ausdrucks, sondern auch dessen Wertkategorie mitteilen. Die Wertekategorie wird durch Referenztypen kodifiziert . lWerte sind &, xWerte sind &&und prWerte sind keine Referenztypen.
StoryTeller - Unslander Monica
@StoryTeller genau, es gibt keinen "natürlichen" Unterschied zwischen Arten von Ausdrücken und Nichtausdrücken. Was die Unterscheidung künstlich macht.
Ofek Shilon
1

Vor C ++ 11 benötigt die Sprache Tools, um zwei verschiedene Arten von Informationen zu erhalten :

  • die Art eines Ausdrucks
  • Der Typ einer Variablen, wie sie deklariert wurde

Aufgrund der Art dieser Informationen mussten die Funktionen in der Sprache hinzugefügt werden (dies ist in einer Bibliothek nicht möglich). Das bedeutet neue Schlüsselwörter. Der Standard hätte dafür zwei neue Schlüsselwörter einführen können. Zum Beispiel exprtype, um den Typ eines Ausdrucks und decltypeden Deklarationstyp einer Variablen abzurufen. Das wäre die klare, glückliche Option gewesen.

Das Standardkomitee hat sich jedoch stets bemüht, die Einführung neuer Schlüsselwörter in die Sprache zu vermeiden , um den Bruch alten Codes zu minimieren. Abwärtskompatibilität ist eine Kernphilosophie der Sprache.

Mit C ++ 11 haben wir also nur ein Schlüsselwort für zwei verschiedene Dinge verwendet : decltype. Die Art und Weise, wie zwischen den beiden Verwendungen unterschieden wird, besteht in der decltype(id-expression)unterschiedlichen Behandlung . Es war eine bewusste Entscheidung des Komitees, ein (kleiner) Kompromiss.

Bolov
quelle
Ich erinnere mich, dass ich das bei einem CPP-Gespräch gehört habe. Ich habe jedoch keine Hoffnung, die Quelle zu finden. Es wäre toll, wenn jemand es findet.
Bolov
1
C ++ hat in der Vergangenheit eine Reihe neuer Schlüsselwörter hinzugefügt, viele ohne Unterstrich und einige mit einem englischen Wort. Das Rationale ist wirklich absurd.
Neugieriger
@curiousguy Jedes eingeführte Schlüsselwort kollidiert mit vorhandenen Benutzersymbolen, sodass das Komitee der Entscheidung, der Sprache neue reservierte Schlüsselwörter hinzuzufügen, große Bedeutung beimisst. Es ist nicht absurd imo.
Bolov
Nicht wahr: exportwurde eingeführt. Wenn Sie können export(zuvor wurden alle Vorlagen standardmäßig "exportiert"), können Sie Dinge wie decltypeund haben constexpr. Offensichtlich wäre das Hinzufügen registerin einer anderen Sprache problematisch.
Neugieriger
@bolov Danke! (1) Ist das Spekulation oder Wissen? Kennen Sie Diskussionen, bei denen das Vermeiden eines zusätzlichen Keywords die Motivation war? (2) Können Sie ein Beispiel geben, bei dem ein ID-Ausdruck tatsächlich anders behandelt werden muss, wenn er "als Ausdruck" verwendet wird? (Was bedeutet das überhaupt?)
Ofek Shilon