TL; DR
Zusätzliche Klammern ändern die Bedeutung eines C ++ - Programms in den folgenden Kontexten:
- Verhindern der argumentabhängigen Namenssuche
- Aktivieren des Kommaoperators in Listenkontexten
- Mehrdeutigkeitsauflösung von ärgerlichen Parsen
- Ableiten von Referenz in
decltype
Ausdrücken
- Verhindern von Präprozessor-Makrofehlern
Argumentabhängige Namenssuche verhindern
Wie in Anhang A der Norm dargelegt, ist a post-fix expression
der Form (expression)
ein primary expression
, aber kein id-expression
und daher kein unqualified-id
. Dies bedeutet, dass die argumentabhängige Namenssuche bei Funktionsaufrufen des Formulars im (fun)(arg)
Vergleich zum herkömmlichen Formular verhindert wird fun(arg)
.
3.4.2 Argumentabhängige Namenssuche [basic.lookup.argdep]
1 Wenn der Postfix-Ausdruck in einem Funktionsaufruf (5.2.2) eine unqualifizierte ID ist , können andere Namespaces durchsucht werden, die bei der üblichen unqualifizierten Suche (3.4.1) nicht berücksichtigt wurden, und in diesen Namespaces die Namespace-Scope-Friend-Funktion oder Funktionsvorlagenerklärungen (11.3), die ansonsten nicht sichtbar sind, können gefunden werden. Diese Änderungen an der Suche hängen von den Arten der Argumente ab (und bei Vorlagenvorlagenargumenten vom Namespace des Vorlagenarguments). [Beispiel:
namespace N {
struct S { };
void f(S);
}
void g() {
N::S s;
f(s); // OK: calls N::f
(f)(s); // error: N::f not considered; parentheses
// prevent argument-dependent lookup
}
- Beispiel beenden]
Aktivieren des Kommaoperators in Listenkontexten
Der Kommaoperator hat in den meisten listenartigen Kontexten (Funktions- und Vorlagenargumente, Initialisierungslisten usw.) eine besondere Bedeutung. Klammern des Formulars a, (b, c), d
in solchen Kontexten können den Kommaoperator im Vergleich zum regulären Formular aktivieren, a, b, c, d
bei dem der Kommaoperator nicht gilt.
5.18 Kommaoperator [expr.comma]
2 In Kontexten, in denen Komma eine besondere Bedeutung erhält, [Beispiel: In Listen von Argumenten für Funktionen (5.2.2) und Listen von Initialisierern (8.5) - Beispiel) kann der in Abschnitt 5 beschriebene Kommaoperator nur in Klammern stehen. [Beispiel:
f(a, (t=3, t+2), c);
hat drei Argumente, von denen das zweite den Wert 5 hat. - Beispiel beenden]
Mehrdeutigkeitsauflösung von ärgerlichen Parsen
Die Abwärtskompatibilität mit C und seiner Deklarationssyntax für arkane Funktionen kann zu überraschenden Parsing-Ambiguitäten führen, die als ärgerliche Parses bezeichnet werden. Im Wesentlichen wird alles, was als Deklaration analysiert werden kann, als eine Deklaration analysiert , obwohl auch eine konkurrierende Analyse gelten würde.
6.8 Mehrdeutigkeitsauflösung [stmt.ambig]
1 In der Grammatik gibt es eine Mehrdeutigkeit, die Ausdrucksanweisungen und Deklarationen umfasst : Eine Ausdrucksanweisung mit einer expliziten Typkonvertierung im Funktionsstil (5.2.3) als äußerster äußerer Unterausdruck kann nicht von einer Deklaration unterschieden werden, bei der der erste Deklarator mit einem (beginnt . In diesen Fällen ist die Erklärung eine Erklärung .
8.2 Mehrdeutigkeitsauflösung [dcl.ambig.res]
1 Die Mehrdeutigkeit, die sich aus der Ähnlichkeit zwischen einem Cast im Funktionsstil und einer in 6.8 genannten Deklaration ergibt, kann auch im Zusammenhang mit einer Deklaration auftreten . In diesem Zusammenhang besteht die Wahl zwischen einer Funktionsdeklaration mit einem redundanten Satz von Klammern um einen Parameternamen und einer Objektdeklaration mit einem Cast im Funktionsstil als Initialisierer. Genau wie bei den in 6.8 erwähnten Unklarheiten besteht die Entschließung darin, jedes Konstrukt, das möglicherweise eine Deklaration sein könnte, als Deklaration zu betrachten . [Hinweis: Eine Deklaration kann explizit durch eine Umwandlung ohne Funktionsstil, durch ein = zur Angabe der Initialisierung oder durch Entfernen der redundanten Klammern um den Parameternamen eindeutig definiert werden. —Ende Anmerkung] [Beispiel:
struct S {
S(int);
};
void foo(double a) {
S w(int(a)); // function declaration
S x(int()); // function declaration
S y((int)a); // object declaration
S z = int(a); // object declaration
}
- Beispiel beenden]
Ein berühmtes Beispiel hierfür ist die Most Vexing Parse , ein Name, der von Scott Meyers in Punkt 6 seines Effective STL- Buches populär gemacht wurde :
ifstream dataFile("ints.dat");
list<int> data(istream_iterator<int>(dataFile), // warning! this doesn't do
istream_iterator<int>()); // what you think it does
Dies deklariert eine Funktion data
, deren Rückgabetyp ist list<int>
. Die Funktionsdaten nehmen zwei Parameter an:
- Der erste Parameter heißt
dataFile
. Es ist Typ ist istream_iterator<int>
. Die Klammern dataFile
sind überflüssig und werden ignoriert.
- Der zweite Parameter hat keinen Namen. Sein Typ ist ein Zeiger auf eine Funktion, die nichts nimmt und ein zurückgibt
istream_iterator<int>
.
Durch Platzieren zusätzlicher Klammern um das erste Funktionsargument (Klammern um das zweite Argument sind unzulässig) wird die Mehrdeutigkeit behoben
list<int> data((istream_iterator<int>(dataFile)), // note new parens
istream_iterator<int>()); // around first argument
// to list's constructor
C ++ 11 verfügt über eine Klammer-Initialisierer-Syntax, mit der solche Analyseprobleme in vielen Kontexten umgangen werden können.
Referenz in decltype
Ausdrücken ableiten
Im Gegensatz zu auto
Typ Abzug decltype
ermöglicht referenceness (L - Wert und R - Wert - Referenzen) abgeleitet werden. Die Regeln unterscheiden zwischen decltype(e)
und decltype((e))
Ausdrücken:
7.1.6.2 Einfache Typspezifizierer [dcl.type.simple]
4 Für einen Ausdruck e
ist der mit bezeichnete Typdecltype(e)
wie folgt definiert:
- Wenn e
es sich um einen nicht gekennzeichneten ID-Ausdruck oder einen nicht gekennzeichneten Zugriff auf Klassenmitglieder handelt (5.2.5), decltype(e)
ist dies der Typ der Entität, die von benannt wird e
. Wenn es keine solche Entität gibt oder wenn e
eine Reihe überladener Funktionen benannt wird, ist das Programm fehlerhaft;
- andernfalls, wenn e
es sich um einen x-Wert handelt, decltype(e)
ist T&&
, wo T
ist der Typ von e
;
- andernfalls, wenn e
es sich um einen Wert decltype(e)
handelt T&
, wo T
ist der Typ von e
;
- Ansonsten decltype(e)
ist die Art von e
.
Der Operand des Decltype-Spezifizierers ist ein nicht bewerteter Operand (Abschnitt 5). [Beispiel:
const int&& foo();
int i;
struct A { double x; };
const A* a = new A();
decltype(foo()) x1 = 0; // 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&
—Ende Beispiel] [Hinweis: Die Regeln zum Bestimmen der beteiligten Typen
decltype(auto)
sind in 7.1.6.4 angegeben. - Endnote]
Die Regeln für decltype(auto)
haben eine ähnliche Bedeutung für zusätzliche Klammern in der rechten Seite des initialisierenden Ausdrucks. Hier ist ein Beispiel aus den C ++ - FAQ und den damit verbundenen Fragen und Antworten
decltype(auto) look_up_a_string_1() { auto str = lookup1(); return str; } //A
decltype(auto) look_up_a_string_2() { auto str = lookup1(); return(str); } //B
Die erste gibt zurück string
, die zweite gibt zurück string &
, was eine Referenz auf die lokale Variable ist str
.
Verhindern von Fehlern im Zusammenhang mit Präprozessormakros
Es gibt eine Vielzahl von Feinheiten mit Präprozessor-Makros in ihrer Interaktion mit der eigentlichen C ++ - Sprache, von denen die häufigsten unten aufgeführt sind
- Verwenden von Klammern um Makroparameter innerhalb der Makrodefinition
#define TIMES(A, B) (A) * (B);
, um unerwünschte Operatorprioritäten zu vermeiden (z. B. TIMES(1 + 2, 2 + 1)
wenn 9 ergibt, aber 6 ohne die Klammern um (A)
und ergibt(B)
- Verwenden von Klammern um Makroargumente mit Kommas:
assert((std::is_same<int, int>::value));
die sonst nicht kompiliert würden
- Verwenden von Klammern um eine Funktion zum Schutz vor Makroerweiterungen in enthaltenen Headern:
(min)(a, b)
(mit dem unerwünschten Nebeneffekt, dass auch ADL deaktiviert wird)
&(C::f)
, ist der Operand von&
immer nochC::f
, nicht wahr?