Коли зайві дужки впливають, крім переваги оператора?


91

Дужки в C ++ використовуються в багатьох місцях: наприклад, у викликах функцій та групуванні виразів, щоб замінити пріоритет оператора. Окрім незаконних зайвих дужок (наприклад, навколо списків аргументів викликів функцій), загальним, але не абсолютним правилом С ++ є те, що зайві дужки ніколи не зашкодять :

5.1 Первинні вирази [expr.prim]

5.1.1 Загальне [прим. Загальний]

6 Вираз у дужках - це основний вираз, тип та значення якого ідентичні виразу, що додається. Наявність дужок не впливає на те, чи є вираз значенням l. Вираз у дужках може використовуватися в точно таких самих контекстах, що і ті, де може використовуватися укладений вираз, і з тим самим значенням, за винятком випадків, коли зазначено інше .

Питання : в яких контекстах додаткові дужки змінюють значення програми на C ++, крім того, щоб замінити пріоритет основного оператора?

ПРИМІТКА . Я вважаю обмеження синтаксису вказівника на член&qualified-id без дужок виходити за межі області дії, оскільки воно обмежує синтаксис, а не дозволяє два синтаксиси з різними значеннями. Подібним чином, використання дужок всередині визначень макросів препроцесора також захищає від небажаного пріоритету оператора.


"Я вважаю дозвіл & (кваліфікованого ідентифікатора) для вказівника на учасника додатком переваги оператора." -- Чому так? Якщо опустити дужки &(C::f), операнд &все ще C::fє, чи не так?

@hvd expr.unary.op/4: Вказівник на член формується лише тоді, коли використовується явний текст &, і його операнд - це кваліфікований ідентифікатор, не вкладений у дужки.
TemplateRex

Правильно, так яке відношення це має до переваги оператора? (Неважливо, ваше відредаговане запитання це прояснює.)

@hvd оновлено, я переплутав RHS із LHS у цьому Запитанні , і там парени використовуються для перевизначення переваги виклику функції ()над селектором вказівника до члена::*
TemplateRex

1
Я думаю, ви повинні бути дещо точнішими щодо того, які справи потребують розгляду. Наприклад, дужки навколо імені типу, щоб зробити його оператором приведення в стилі C (незалежно від контексту), взагалі не створюють вираз у дужках. З іншого боку, я б сказав технічно, що умова після if або while є виразом у дужках, але оскільки дужки тут є частиною синтаксису, їх не слід розглядати. Також не повинно бути IMO в будь-якому випадку, коли без дужок вираз більше не буде аналізуватися як одна одиниця, незалежно від того, задіяний пріоритет оператора чи ні.
Марк ван Левен

Відповіді:


112

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)

7
Це насправді не змінює значення програми, але найкраща практика та впливає на попередження, видані компілятором: додаткові дужки слід використовувати в if/ whileякщо вираз є призначенням. Наприклад if (a = b)- попередження (ви мали на увазі ==?), Тоді як if ((a = b))- жодного попередження.
Csq

@Csq дякую, гарне спостереження, але це попередження певного компілятора, а не передбачене стандартом. Я не думаю, що це відповідає мовно-адвокатському характеру цього запитання.
TemplateRex

Чи є (min)(a, b)(із злим МАКРО min(A, B)) частиною запобігання пошуку аргументів залежно від аргументів?
Jarod42

@ Jarod42 Я думаю, що так, але давайте розглянемо такі та інші злі макроси поза сферою питання :-)
TemplateRex

5
@JamesKanze: Зверніть увагу, що OP та TemplateRex - це одна і та ж особа ^ _ ^
Jarod42,

4

Загалом, у мовах програмування «зайві» дужки означають, що вони не змінюють синтаксичний порядок синтаксичного аналізу чи значення. Вони додаються для уточнення порядку (переваги оператора) на користь людей, які читають код, і єдиним їх ефектом буде трохи уповільнення процесу компіляції та зменшення людських помилок у розумінні коду (ймовірно, прискорення загального процесу розробки ).

Якщо набір дужок фактично змінює спосіб синтаксичного аналізу виразу, то вони за визначенням не є зайвими. Дужки, які перетворюють незаконний / недійсний синтаксичний розбір на законний, не є "зайвими", хоча це може свідчити про поганий мовний дизайн.


2
точно, і це є загальним правилом і на С ++ (див. Стандартну цитату у питанні), за винятком випадків, коли зазначено інше . Вказати на ці "слабкі місця" було метою цього запитання та відповіді.
TemplateRex
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.