什么时候额外的括号会影响运算符的优先级?

C ++中的括号用在许多地方:例如在函数调用和分组expression式中覆盖运算符优先级。 除了非法的额外括号 (比如函数调用参数列表)之外,C ++的一个通用但不是绝对的规则就是额外的括号不会受到伤害

5.1主expression式[expr.prim]

5.1.1一般[expr.prim.general]

6加括号的expression式是一个主expression式,其types和值与所附expression式的types和值相同。 括号的存在不影响expression式是否左值。 带括号的expression式可以用在与包含的expression式可以使用的expression式完全相同的上下文中, 除非另有说明,否则使用相同的含义。

问题 :除了覆盖基本的运算符优先级之外,在哪些上下文中,额外的括号会改变C ++程序的含义?

:我认为指针到成员语法限制为不带圆括号的&qualified-id超出范围,因为它限制了语法,而不是允许两个含义不同的语法。 同样, 在预处理器macros定义中使用括号也可以防止不需要的运算符优先级。

TL; DR

额外的圆括号在以下上下文中改变了C ++程序的含义:

  • 防止参数相关的名称查找
  • 在列表上下文中启用逗号运算符
  • 令人烦恼的parsing的模棱两可的解决scheme
  • 推断decltypeexpression式的参考性
  • 防止预处理器macros错误

防止依赖于参数的名称查找

正如标准的附录A所详述的,表单(expression)post-fix expression是一个primary expression ,但不是一个id-expression ,因此不是一个unqualified-id 。 这意味着与传统formsfun(arg)相比,在forms(fun)(arg)函数调用中阻止了与参数相关的名称查找。

3.4.2基于参数的名称查找[basic.lookup.argdep]

1当函数调用(5.2.2)中的postfix-expression是非限定id时 ,可以search在通常的非限定查找(3.4.1)期间未考虑的其他名称空间,并且在这些名称空间中可以search名称空间范围的friend函数或可能会发现函数模板声明(11.3)不可见。 这些对search的修改取决于参数的types(以及模板参数,模板参数的名称空间)。 [例如:

 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), dformsa, (b, c), d括号可以使逗号运算符与逗号运算符不适用的常规formsa, b, c, d相比。

5.18逗号运算符[expr.comma]

2在逗号赋予特殊意义的上下文中,[例子: 在函数参数列表(5.2.2)和初始值设定项列表(8.5) –范例]中,第5章中描述的逗号运算符只能出现在括号内。 [例如:

 f(a, (t=3, t+2), c); 

有三个参数,其中第二个参数的值是5. -end example]

困惑的parsing的歧义解决

与C及其神秘函数声明语法的向后兼容性可能会导致令人惊讶的parsing歧义,称为烦人的parsing。 从本质上讲, 任何可以被parsing为声明的东西都将被parsing为一个 ,尽pipe竞争parsing也将被应用。

6.8歧义解决[stmt.ambig]

1 在涉及expression式语句和声明的语法中存在一个不明确的地方 :一个具有函数式显式types转换(5.2.3)作为其最左边的子expression式的expression式语句可以与第一个声明符以( 在这些情况下,声明是一个声明

8.2歧义parsing[dcl.ambig.res]

1 由于函数风格types转换和6.8中提到的声明之间的相似性而产生的歧义也可能发生在声明的上下文中 。 在这种情况下,select是在函数声明与带有冗余括号的函数声明之间,函数声明使用函数样式强制转换为初始值设定项。 正如6.8中提到的含糊之处一样,决议是考虑任何可能成为声明的结构 。 [注意:一个声明可以被一个无效types的转换明确地消除,用=来表示初始化,或者去除参数名称周围多余的括号。 – 结束] [例子:

 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 } 

– 例子]

一个着名的例子就是“ 最痛苦的parsing” ,这是Scott Meyers在其Effective STL书第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 ,它的返回types是list<int> 。 函数数据有两个参数:

  • 第一个参数被命名为dataFile 。 它的types是istream_iterator<int>dataFile周围的括号是多余的,并被忽略。
  • 第二个参数没有名字。 它的types是指向函数的指针,并返回一个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具有支持初始化器的语法,允许在许多情况下支持这种parsing问题。

推导decltypeexpression式的参考性

autotypes推导相比, decltype允许推导参考(左值和右值参考)。 规则区分decltype(e)decltype((e))expression式:

7.1.6.2简单types说明符[dcl.type.simple]

4对于expression式edecltype(e)表示的types定义如下:

– 如果e是未经授权的idexpression式或未经授权的类成员访问(5.2.5),则decltype(e)是由e命名的实体的types。 如果没有这样的实体,或者如果e名称是一组重载的函数,则该程序是不合格的;

– 否则,如果e是一个x值,则decltype(e)T&& ,其中Te的types;

否则,如果e是左值,则decltype(e)T& ,其中Te的types;

否则, decltype(e)decltype(e)的types。

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)types的规则在7.1.6.4中指定。 – 注意]

decltype(auto)的规则与初始化expression式的RHS中的额外括号具有相似的含义。 这里有一个来自C ++ FAQ这个相关Q&A的例子

 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 & ,它是对局部variablesstr的引用。

防止预处理器macros相关的错误

预处理器macros与C ++语言本身的相互作用有许多微妙之处,其中最常见的是下面列出的

  • #define TIMES(A, B) (A) * (B);在macros定义内使用括号#define TIMES(A, B) (A) * (B); 为了避免不必要的运算符优先级(例如在产生9但在(A)(B)周围没有括号的情况下产生6的TIMES(1 + 2, 2 + 1) (B)
  • 在内部使用逗号的macros参数周围使用括号: assert((std::is_same<int, int>::value)); 否则不会编译
  • (min)(a, b) (同时也禁用ADL的不良副作用)

一般来说,在编程语言中,“额外”括号意味着它们不会改变语法分析顺序或含义。 他们被添加来阐明为了读取代码的人的利益(操作符优先级),它们的唯一影响是稍微减慢编译过程,减less人为的错误理解代码(可能加快整个开发过程)。

如果一组圆括号实际上改变了expression式的parsing方式,那么它们在定义上不是额外的。 把非法/无效的parsing变成合法parsing的括号不是“额外的”,尽pipe这可能指出了糟糕的语言devise。