Когда лишние круглые скобки влияют, кроме приоритета оператора?

91

Круглые скобки в C ++ используются во многих местах: например, в вызовах функций и выражениях группировки для переопределения приоритета операторов. Помимо недопустимых дополнительных круглых скобок (например, вокруг списков аргументов вызова функций), общее, но не абсолютное правило C ++ состоит в том, что лишние круглые скобки никогда не повредят :

5.1 Первичные выражения [expr.prim]

5.1.1 Общие [expr.prim.general]

6 Выражение в скобках - это первичное выражение, тип и значение которого идентичны таковым в заключенном выражении. Наличие круглых скобок не влияет на то, является ли выражение значением l. Выражение в скобках может использоваться в тех же контекстах, что и заключенное в него выражение, и с тем же значением, если не указано иное .

Вопрос : в каких контекстах дополнительные круглые скобки изменяют смысл программы на C ++, кроме отмены приоритета базовых операторов?

ПРИМЕЧАНИЕ . Я считаю, что ограничение синтаксиса указателя на член&qualified-id без круглых скобок выходит за рамки области применения, поскольку оно ограничивает синтаксис, а не позволяет использовать два синтаксиса с разными значениями. Точно так же использование круглых скобок внутри определений макросов препроцессора также защищает от нежелательного приоритета оператора.

TemplateRex
источник
«Я считаю, что разрешение & (квалифицированный идентификатор) для указателя на член является применением приоритета оператора». -- Почему это? Если опустить скобки в &(C::f), операнд &по - прежнему C::f, не так ли?
@hvd expr.unary.op/4: указатель на член формируется только тогда, когда используется явное указание, &а его операнд представляет собой квалифицированный идентификатор, не заключенный в круглые скобки.
TemplateRex
Итак, какое это имеет отношение к приоритету операторов? (Неважно, ваш отредактированный вопрос проясняет это.)
@hvd updated, в этом Q&A я путал RHS с LHS , и там скобки используются для переопределения приоритета вызова функции ()над селектором указателя на член::*
TemplateRex
1
Я думаю, вам следует уточнить, какие случаи требуют рассмотрения. Например, круглые скобки вокруг имени типа, чтобы сделать его оператором приведения в стиле C (независимо от контекста), вообще не создают выражения в скобках. С другой стороны, я бы сказал, что технически условие после if или while является выражением в скобках, но поскольку круглые скобки являются здесь частью синтаксиса, их не следует рассматривать. И ИМО не должно быть в любом случае, когда без круглых скобок выражение больше не будет анализироваться как единое целое, независимо от приоритета оператора или нет.
Marc van Leeuwen

Ответы:

113

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);   // 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 и его загадочным синтаксисом объявления функций может привести к неожиданным двусмысленностям синтаксического анализа, известным как досадный синтаксический анализ. По сути, все, что может быть проанализировано как объявление, будет анализироваться как одно , даже если конкурирующий синтаксический анализ также будет применяться.

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
}

—Конечный пример]

Известным примером этого является Most Vexing Parse , имя, популяризированное Скоттом Мейерсом в пункте 6 его книги Effective 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's constructor

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;

- в противном случае, если elvalue, 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;   // 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)имеют аналогичное значение для дополнительных круглых скобок в правой части инициализирующего выражения. Вот пример из FAQ 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.

Предотвращение ошибок, связанных с макросами препроцессора

При взаимодействии макросов препроцессора с самим языком 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)
TemplateRex
источник
7
На самом деле не меняет смысла программы, но лучше всего и влияет на предупреждения, выдаваемые компилятором: дополнительные скобки должны использоваться в if/, whileесли выражение является присваиванием. Например if (a = b)- предупреждение (вы имели в виду ==?), А if ((a = b))- без предупреждения.
Csq 09
@Csq спасибо, хорошее наблюдение, но это предупреждение конкретного компилятора, а не стандарт. Я не думаю, что это укладывается в рамки вопросов и ответов языкового юриста.
TemplateRex
Является ли (min)(a, b)(со злым МАКРОСОМ min(A, B)) частью предотвращения поиска имени, зависящего от аргументов?
Jarod42 09
@ Jarod42 Думаю, да, но давайте рассмотрим, что такие и другие злые макросы выходят за рамки вопроса :-)
TemplateRex
5
@JamesKanze: Обратите внимание, что OP и TemplateRex - это одно и то же лицо ^ _ ^
Jarod42
4

Как правило, в языках программирования «лишние» скобки означают, что они не меняют порядок синтаксического анализа или значение. Они добавляются для уточнения порядка (приоритета операторов) в интересах людей, читающих код, и их единственный эффект будет заключаться в небольшом замедлении процесса компиляции и уменьшении человеческих ошибок при понимании кода (возможно, ускорение общего процесса разработки. ).

Если набор круглых скобок действительно меняет способ анализа выражения, то они по определению не являются дополнительными. Скобки, которые превращают незаконный / недействительный синтаксический анализ в допустимый, не являются «лишними», хотя это может указывать на плохой языковой дизайн.

Фил Перри
источник
2
точно, и это также общее правило для C ++ (см. Стандартную цитату в вопросе), если не указано иное . Указать на эти «слабые места» и было целью этого Q&A.
TemplateRex