Ответ 1
TL; DR
Дополнительные круглые скобки изменяют значение программы на С++ в следующих контекстах:
- предотвращение поиска зависимых от аргументов имен
- включение оператора запятой в контекстах списка
- двусмысленное разрешение досадных парсов
- вывод ссылочной категории в выражениях
decltype
- предупреждение макропроцессора препроцессора
Предотвращение поиска зависимых от аргументов имен
Как подробно описано в Приложении A Стандарта, a post-fix expression
формы (expression)
является primary expression
, но не id-expression
, и поэтому не является unqualified-id
. Это означает, что зависающий от имени поиск имени предотвращается при вызове функций формы (fun)(arg)
по сравнению с обычной формой fun(arg)
.
3.4.2 Поиск зависимых от аргументов имен [basic.lookup.argdep]
1 Когда постфиксное выражение в вызове функции (5.2.2) является unqualified-id, другие пространства имен, не учтенные во время обычного неквалифицированный поиск (3.4.1), и в этих пространствах имен, функция имени пространства имен или назначение шаблона функции (11.3), которые не видны другим образом. Эти изменения в поиск зависит от типов аргументов (и шаблона шаблона аргументы, пространство имен аргумента шаблона). [Пример:
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
}
-end пример]
Включение оператора запятой в контекстах списка
Оператор запятой имеет особое значение в большинстве контекстов (например, аргументы функции и шаблона, списки инициализаторов и т.д.). Скобки формы a, (b, c), d
в таких контекстах могут включать запятый оператор по сравнению с регулярной формой a, b, c, d
, где оператор запятой не применяется.
5.18 Comma operator [expr.comma]
2 В контекстах, где запятая имеет особое значение, [Пример: в списки аргументов к функциям (5.2.2) и списки инициализаторов (8.5) -end example] оператор запятой, как описано в разделе 5, может появляются только в круглых скобках. [Пример:
f(a, (t=3, t+2), c);
имеет три аргумента, второй из которых имеет значение 5. -end example ]
Разрешение неоднозначности досадных разбора
Обратная совместимость с синтаксисом объявления C и его тайной функции может привести к неожиданной разборчивости разбора, известной как досадные разборки. По сути, все, что может быть проанализировано как объявление, будет анализироваться как один, даже если будет применен и конкурирующий синтаксический анализ.
6.8 Разрешение неоднозначности [stmt.ambig]
1 В грамматике есть двусмысленность, включающая выражения-выражения и объявления: выражение-оператор с функциональным стилем явное преобразование типа (5.2.3), поскольку его самое левое подвыражение может быть неотличимы от декларации, где начинается первый декларатор с. (). В этих случаях утверждение представляет собой объявление.
8.2 Разрешение неоднозначности [dcl.ambig.res]
1 Неоднозначность, возникающая из-за сходства между функциональным стилем и заявка, упомянутая в 6.8, может также возникать в контексте объявления. В этом контексте выбор между функцией объявление с избыточным набором круглых скобок вокруг параметра имя и объявление объекта с помощью функции-стиля в качестве инициализатор. Так же как и для двусмысленностей, упомянутых в 6.8, разрешение рассматривает любую конструкцию, которая может быть объявить декларацию. [Примечание: объявление может быть явно неоднозначно с помощью неэффективного стиля, с помощью = указать инициализации или удаления избыточных круглых скобок вокруг имя параметра. -end note] [Пример:
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
}
-end пример]
Известным примером этого является Самый Vexing Parse, имя, популяризированное Скоттом Мейерсом в пункте 6 его Эффективная версия STL:
ifstream dataFile("ints.dat");
list<int> data(istream_iterator<int>(dataFile), // warning! this doesn't do
istream_iterator<int>()); // what you think it does
Объявляет функцию data
, возвращаемый тип которой list<int>
.
данные функции принимают два параметра:
- Первый параметр называется
dataFile
. Он имеет типistream_iterator<int>
. круглые скобки вокругdataFile
являются излишними и игнорируются. - Второй параметр не имеет имени. Его тип - указатель на выполнение функции
ничего и возвращая
istream_iterator<int>
.
Размещение дополнительных круглых скобок вокруг первого аргумента функции (круглые скобки вокруг второго аргумента являются незаконными) разрешит двусмысленность
list<int> data((istream_iterator<int>(dataFile)), // note new parens
istream_iterator<int>()); // around first argument
// to list constructor
С++ 11 имеет синтаксис синтаксического набора, который позволяет во многих контекстах решать такие проблемы синтаксического анализа.
Выделение референциальности в выражениях decltype
В отличие от вычитания типа auto
, decltype
позволяет определить ссылочность (ссылки lvalue и rvalue). Правила различают выражения decltype(e)
и decltype((e))
:
7.1.6.2 Спецификаторы простого типа [dcl.type.simple]
4 Для выражения
e
, тип, обозначенныйdecltype(e)
, определяется как следует:- если
e
- это неравномерное id-выражение или unparenthesized class member access (5.2.5),decltype(e)
- это тип объекта, названногоe
. Если такой объект отсутствует или еслиe
называет набор перегруженных функций, программа плохо сформирована;- в противном случае, если
e
- значение x,decltype(e)
-T&&
, гдеT
- типe
;- в противном случае, если
e
является lvalue,decltype(e)
являетсяT&
, гдеT
- тип ofe
;- в противном случае
decltype(e)
является типомe
.Операнд Спецификатор decltype является неоцененным операндом (п. 5). [Пример:
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&
-end example] [Примечание: правила для определения типов, включающих
decltype(auto)
указаны в 7.1.6.4. -end note]
Правила для decltype(auto)
имеют аналогичное значение для дополнительных круглых скобок в RHS инициализирующего выражения. Вот пример из С++ FAQ и этот связанные с Q & A
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
Первый возвращает string
, второй возвращает string &
, который является ссылкой на локальную переменную str
.
Предотвращение макросов, связанных с макропроцессором
Существует множество тонкостей с макросами препроцессора в их взаимодействии с языком С++, наиболее распространенные из которых перечислены ниже
- используя круглые скобки вокруг макроопределений внутри определения макроса
#define TIMES(A, B) (A) * (B);
, чтобы избежать нежелательного приоритета оператора (например, вTIMES(1 + 2, 2 + 1)
, который дает 9, но даст 6 без круглых скобок вокруг(A)
и(B)
- с помощью круглых скобок вокруг макросов с запятыми внутри:
assert((std::is_same<int, int>::value));
, которые иначе не скомпилировались - с помощью круглых скобок вокруг функции для защиты от макрорасширения в включенных заголовках:
(min)(a, b)
(с нежелательным побочным эффектом также отключение ADL)