TL; DR
Дополнительные круглые скобки изменяют значение программы на C ++ в следующих контекстах:
- предотвращение поиска имени, зависящего от аргумента
- включение оператора запятой в контекстах списка
- разрешение неоднозначности досадных разборов
- вывод референции в
decltype
выражениях
- предотвращение ошибок макроса препроцессора
Предотвращение поиска имени, зависящего от аргумента
Как подробно описано в Приложении A к Стандарту, a post-fix expression
формы (expression)
- это a primary expression
, но не id-expression
, и, следовательно, не unqualified-id
. Это означает, что поиск имени, зависящий от аргументов, предотвращается при вызовах функций формы (fun)(arg)
по сравнению с обычной формой fun(arg)
.
3.4.2 Поиск имени в зависимости от аргумента [basic.lookup.argdep]
1 Когда постфиксное-выражение в вызове функции (5.2.2) является неквалифицированным идентификатором , другие пространства имен, не учитываемые во время обычного неквалифицированного поиска (3.4.1), могут быть найдены, и в этих пространствах имен может выполняться поиск дружественной функции области пространства имен или могут быть найдены объявления шаблонов функций (11.3), которые иначе не видны. Эти изменения в поиске зависят от типов аргументов (а для аргументов шаблона шаблона - пространства имен аргумента шаблона). [ Пример:
namespace N {
struct S { };
void f(S);
}
void g() {
N::S s;
f(s);
(f)(s);
}
—Конечный пример]
Включение оператора запятой в контекстах списка
Оператор запятая имеет особое значение в большинстве контекстов, подобных списку (аргументы функции и шаблона, списки инициализаторов и т. Д.). Скобки формы a, (b, c), d
в таких контекстах могут включать оператор запятой по сравнению с обычной формой, a, b, c, d
где оператор запятой не применяется.
5.18 Оператор запятой [expr.comma]
2 В контекстах, где запятой придается особое значение, [Пример: в списках аргументов функций (5.2.2) и списках инициализаторов (8.5) - пример конца] оператор запятой, как описано в разделе 5, может появляться только в круглых скобках. [ Пример:
f(a, (t=3, t+2), c);
имеет три аргумента, второй из которых имеет значение 5. —конечный пример]
Разрешение неоднозначности неприятных синтаксических разборов
Обратная совместимость с C и его загадочным синтаксисом объявления функций может привести к неожиданным двусмысленностям синтаксического анализа, известным как досадный синтаксический анализ. По сути, все, что может быть проанализировано как объявление, будет анализироваться как одно , даже если конкурирующий синтаксический анализ также будет применяться.
6.8 Разрешение неоднозначности [stmt.ambig]
1 В грамматике, связанной с операторами-выражениями и объявлениями, присутствует двусмысленность : оператор-выражение с явным преобразованием типа в стиле функции (5.2.3) в качестве крайнего левого подвыражения может быть неотличим от объявления, в котором первый декларатор начинается с ( . В этих случаях заявление является декларацией .
8.2 Разрешение неоднозначности [dcl.ambig.res]
1 Неоднозначность, возникающая из-за сходства между приведением типа функции и объявлением, упомянутым в 6.8, также может возникать в контексте объявления . В этом контексте выбор находится между объявлением функции с избыточным набором круглых скобок вокруг имени параметра и объявлением объекта с приведением в стиле функции в качестве инициализатора. Как и в случае двусмысленностей, упомянутых в 6.8, резолюция должна рассматривать любую конструкцию, которая могла бы быть объявлением, объявлением . [Примечание: объявление может быть явно устранено с помощью приведения в не функциональном стиле, с помощью знака = для обозначения инициализации или путем удаления повторяющихся скобок вокруг имени параметра. - конец примечания] [Пример:
struct S {
S(int);
};
void foo(double a) {
S w(int(a));
S x(int());
S y((int)a);
S z = int(a);
}
—Конечный пример]
Известным примером этого является Most Vexing Parse , имя, популяризированное Скоттом Мейерсом в пункте 6 его книги Effective STL :
ifstream dataFile("ints.dat");
list<int> data(istream_iterator<int>(dataFile),
istream_iterator<int>());
Это объявляет функцию, data
возвращаемый тип которой list<int>
. Данные функции принимают два параметра:
- Имя первого параметра
dataFile
. Это типа есть istream_iterator<int>
. Круглые скобки dataFile
излишни и игнорируются.
- Второй параметр не имеет названия. Его тип - указатель на функцию, которая ничего не берет и возвращает
istream_iterator<int>
.
Добавление дополнительных круглых скобок вокруг первого аргумента функции (круглые скобки вокруг второго аргумента недопустимы) устранит двусмысленность
list<int> data((istream_iterator<int>(dataFile)),
istream_iterator<int>());
C ++ 11 имеет синтаксис инициализатора фигурных скобок, который позволяет обходить такие проблемы синтаксического анализа во многих контекстах.
Выведение референсов в decltype
выражениях
В отличие от auto
вывода типов, decltype
позволяет выводить ссылки (ссылки lvalue и rvalue). Правила различать decltype(e)
и decltype((e))
выражения:
7.1.6.2 Спецификаторы простого типа [dcl.type.simple]
4 Для выражения e
, тип обозначаетсяdecltype(e)
определяется следующим образом :
- if e
- это выражение id без скобок или доступ к члену класса без скобок (5.2.5), decltype(e)
- это тип сущности, названной by e
. Если такой сущности нет или если она e
называет набор перегруженных функций, программа плохо сформирована;
- в противном случае, если e
- значение x, decltype(e)
это T&&
, где T
- тип e
;
- в противном случае, если e
lvalue, decltype(e)
is T&
, где T
- тип e
;
- в противном случае decltype(e)
это тип e
.
Операнд спецификатора decltype - это неоцененный операнд (раздел 5). [ Пример:
const int&& foo();
int i;
struct A { double x; };
const A* a = new A();
decltype(foo()) x1 = 0;
decltype(i) x2;
decltype(a->x) x3;
decltype((a->x)) x4 = x3;
—Конечный пример] [Примечание: правила определения типов, включающих в себя
decltype(auto)
, указаны в 7.1.6.4. - конец примечания]
Правила для decltype(auto)
имеют аналогичное значение для дополнительных круглых скобок в правой части инициализирующего выражения. Вот пример из FAQ C ++ и связанных вопросов и ответов
decltype(auto) look_up_a_string_1() { auto str = lookup1(); return str; }
decltype(auto) look_up_a_string_2() { auto str = lookup1(); return(str); }
Первый возвращается string
, второй возвращается string &
, что является ссылкой на локальную переменную str
.
Предотвращение ошибок, связанных с макросами препроцессора
При взаимодействии макросов препроцессора с самим языком C ++ существует множество тонкостей, наиболее распространенные из которых перечислены ниже.
- использование круглых скобок вокруг параметров макроса внутри определения макроса
#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)
&(C::f)
, операнд&
по - прежнемуC::f
, не так ли?