Когда дополнительные скобки имеют эффект, отличные от приоритета оператора?
Скобки в C ++ используются во многих местах: например, в вызовах функций и группировании выражений для переопределения приоритета оператора. Помимо незаконных дополнительных круглых скобок (например, вокруг списков аргументов вызова функций), общее, но не абсолютное правило C ++ заключается в том, что дополнительные круглые скобки никогда не повреждаются :
5.1 Первичные выражения [expr.prim]
5.1.1 Общие положения [expr.prim.general]
- Какой твой любимый мультфильм «программист»?
- Что вы наиболее противоречивое мнение программирования?
- Почему в Java нет множественного наследования, но допускается реализация нескольких интерфейсов?
- Сон () злой?
- Какова конкретная проблема с множественным наследованием?
6 Выражение в скобках – это первичное выражение, тип и значение которого совпадают с типом заключенного выражения. Наличие скобок не влияет на то, является ли выражение lvalue. Выражение в скобках может использоваться в точно таких же контекстах, как те, в которых может использоваться замкнутое выражение, и с тем же значением, если не указано иное .
Вопрос : в каких контекстах дополнительные круглые скобки меняют смысл программы на C ++, кроме переопределения основного приоритета оператора?
ПРИМЕЧАНИЕ . Я рассматриваю ограничение синтаксиса указателя на член с &qualified-id
без круглых скобок, чтобы быть вне области видимости, поскольку он ограничивает синтаксис, а не позволяет использовать два синтаксиса с разными значениями. Точно так же использование скобок в макропрофилях препроцессора также защищает от нежелательного приоритета оператора.
TL; DR
Дополнительные круглые скобки меняют смысл программы на C ++ в следующих контекстах:
- предотrotation поиска зависимых от аргументов имен
- включение оператора запятой в контекстах списка
- двусмысленное разрешение досадных анализов
-
decltype
референциальности в выраженияхdecltype
- предотrotation макропроцессов препроцессора
Предотrotation поиска зависимых от аргументов имен
Как подробно описано в Приложении А стандарта, post-fix expression
формы (expression)
является primary expression
, но не выражением id-expression
и, следовательно, не является unqualified-id
. Это означает, что зависящий от аргументов поиск имени предотвращается при вызове функций формы (fun)(arg)
по сравнению с обычной формой fun(arg)
.
3.4.2. Поиск зависимого от аргумента имени [basic.lookup.argdep]
1 Когда постфиксное выражение в вызове функции (5.2.2) является неквалифицированным-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 и его тайной функции может привести к неожиданной parsingчивости parsingа, известной как досадные parsingки. По сути, все, что может быть проанализировано как объявление, будет анализироваться как единое целое , даже если будет применяться и конкурирующий синтаксический анализ.
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 пример]
Известным примером этого является « Самый неприятный парсер» , имя, популяризированное Скоттом Мейерсом в 6-м пункте его эффективной книги STL :
ifstream dataFile("ints.dat"); list data(istream_iterator (dataFile), // warning! this doesn't do istream_iterator ()); // what you think it does
Это объявляет функцию, data
, чей тип возврата – это list
. Данные функции принимают два параметра:
- Первый параметр называется
dataFile
. Это типistream_iterator
.dataFile
вокругdataFile
являются излишними и игнорируются. - Второй параметр не имеет имени. Его тип – это указатель на функцию, которая ничего не принимает и возвращает
istream_iterator
.
Размещение дополнительных круглых скобок вокруг первого аргумента функции (круглые скобки вокруг второго аргумента являются незаконными) разрешит двусмысленность
list data((istream_iterator (dataFile)), // note new parens istream_iterator ()); // around first argument // to list's constructor
В C ++ 11 есть синтаксис синтаксиса, который позволяет во многих контекстах решать такие проблемы синтаксического анализа.
Выделение референциальности в выражениях decltype
В отличие от вычитания типа auto
, decltype
позволяет decltype
ссылки (значения lvalue и rvalue). Правила различают decltype(e)
и decltype((e))
:
7.1.6.2 Спецификаторы простого типа [dcl.type.simple]
4 Для выражения
e
тип, обозначаемыйdecltype(e)
, определяется следующим образом:– если
e
– это несферизованное идентификационное выражение или unparenthesized доступ к члену classа (5.2.5),decltype(e)
– это тип объекта, названногоe
. Если такой объект отсутствует или еслиe
называет набор перегруженных функций, программа плохо сформирована;– в противном случае, если
e
является значением x,decltype(e)
являетсяT&&
, гдеT
– типe
;– в противном случае, если
e
является lvalue,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&
-End example] [Примечание. Правила для определения типов с участием
decltype(auto)
указаны в 7.1.6.4. -End note]
Правила для 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
.
Предотrotation макросов, связанных с макропроцессором
Существует множество тонкостей с макросами препроцессора в их взаимодействии с языком C ++, наиболее распространенные из которых перечислены ниже
- используя круглые скобки вокруг параметров макроса внутри определения макроса
#define TIMES(A, B) (A) * (B);
чтобы избежать нежелательного приоритета оператора (например, вTIMES(1 + 2, 2 + 1)
который дает 9, но даст 6 без круглых скобок вокруг(A)
и(B)
- используя круглые скобки вокруг макросов с запятыми внутри:
assert((std::is_same
которые иначе не компилировались бы::value)); - используя круглые скобки вокруг функции для защиты от макрорасширения в включенных заголовках:
(min)(a, b)
(с нежелательным побочным эффектом также отключение ADL)
В общем, в языках программирования «дополнительные» круглые скобки подразумевают, что они не меняют синтаксический порядок или значение синтаксического анализа. Они добавляются, чтобы уточнить порядок (приоритет оператора) для людей, читающих код, и их единственным эффектом было бы немного замедлить процесс компиляции и уменьшить человеческие ошибки в понимании кода (возможно, ускорить общий процесс разработки ).
Если набор круглых скобок фактически изменяет способ анализа, то они по определению не являются дополнительными. Скобки, которые превращают незаконный / недействительный анализ в юридический, не являются «лишними», хотя это может указывать на плохой дизайн языка.