TL; DR
Додаткові дужки змінюють значення програми на C ++ у таких контекстах:
- запобігання пошуку аргументованого імені
- увімкнення оператора коми у контексті списку
- розмитість двозначності дратівливих розборів
- виведення довідковості у
decltype
виразах
- запобігання помилкам макросу препроцесора
Запобігання пошуку аргументованого імені
Як детально описано у Додатку А до Стандарту, 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); // OK: calls N::f
(f)(s); // error: N::f not considered; parentheses
// prevent argument-dependent lookup
}
—Кінцевий приклад]
Увімкнення оператора коми у контексті списку
Оператор кома має особливе значення в більшості подібних до списку контекстів (аргументи функції та шаблону, списки ініціалізаторів тощо). Дужки форми 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 та його синтаксисом декларації функцій arcane може призвести до дивовижних неоднозначностей синтаксичного аналізу, відомих як неприємні аналізи. По суті, все, що може бути проаналізовано як декларація, буде проаналізовано як одне , навіть незважаючи на те, що також застосовуватиметься синтаксичний аналіз.
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)); // function declaration
S x(int()); // function declaration
S y((int)a); // object declaration
S z = int(a); // object declaration
}
—Кінцевий приклад]
Відомим прикладом цього є найбільш неприємний розбір , ім'я, яке популяризував Скотт Мейерс у пункті 6 своєї книги " Ефективна версія" .
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's constructor
C ++ 11 має синтаксис дужки-ініціалізатора, що дозволяє побічно виконувати такі проблеми синтаксичного аналізу в багатьох контекстах.
Виведення референтності у decltype
виразах
На відміну від auto
вирахування типу, decltype
дозволяє виводити посилання (посилання lvalue та rvalue). Правила розрізняти decltype(e)
і decltype((e))
вирази:
7.1.6.2 Прості специфікатори типу [dcl.type.simple]
4 Для вираження e
, тип позначаєтьсяdecltype(e)
визначається наступним чином :
- якщо e
є непрозорим виразом ідентифікатора або непрозорим доступом до класу (5.2.5), decltype(e)
це тип сутності, названої e
. Якщо такої сутності немає або якщо називається e
набір перевантажених функцій, програма неправильно сформована;
- інакше, якщо e
є значення x, decltype(e)
є T&&
, де T
тип e
;
- інакше, якщо e
є значення, decltype(e)
є 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; // 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&
—Кінцевий приклад] [Примітка: Правила визначення типів, що включають
decltype(auto)
, вказані в 7.1.6.4. —Кінець примітки]
Правила для decltype(auto)
мають подібне значення для зайвих дужок у RHS ініціалізуючого виразу. Ось приклад із поширених запитань про C ++ та відповідні запитання та відповіді
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)
&(C::f)
, операнд&
все щеC::f
є, чи не так?