当do额外的括号有影响时,除了运算符优先级

When do extra parentheses have an effect, other than on operator precedence?

本文关键字:优先级 运算符 有影响 do      更新时间:2023-10-16

C++中的括号在很多地方都有使用:例如,在函数调用和分组表达式中,用以覆盖运算符优先级除了非法的额外圆括号(例如函数调用参数列表周围),C++的一个通用但非绝对规则是额外圆括号永远不会伤害

5.1主要表达式[expr.prim]

5.1.1概述[expr.prim.General]

6带括号的表达式是其类型和值与所附表达式的值相同。出席括号的大小不会影响表达式是否为左值。带括号的表达式可以在完全相同的上下文中使用与那些可以使用封闭表达式的地方一样意思是,除非另有说明

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

注意:我认为将指向成员的指针语法限制为不带括号的&qualified-id超出了范围,因为它限制了语法,而不允许使用两种含义不同的语法。类似地,在预处理器宏定义中使用括号也可以防止不需要的运算符优先级。

TL;DR

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

  • 阻止依赖于参数的名称查找
  • 在列表上下文中启用逗号运算符
  • 烦恼解析的模糊度求解
  • 推导decltype表达式中的指称性
  • 防止预处理器宏错误

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

如本标准附录A所述,(expression)形式的post-fix expressionprimary 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
}

-结束示例]

在列表上下文中启用逗号运算符

逗号运算符在大多数类似列表的上下文(函数和模板参数、初始值设定项列表等)中具有特殊含义。与不应用逗号运算符的常规形式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,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,其返回类型为list<int>。这个函数数据有两个参数:

  • 第一个参数命名为dataFile。它的类型是istream_iterator<int>。这个CCD_ 16周围的括号是多余的并且被忽略
  • 第二个参数没有名称。它的类型是指向函数获取的指针什么都没有并且返回CCD_ 17

在第一个函数参数周围放置额外的括号(第二个参数周围的括号是非法的)将解决的歧义

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允许推导引用性(左值和右值引用)。区分decltype(e)decltype((e))表达式的规则:

7.1.6.2简单类型说明符[dcl.type.Simple]

4对于表达式edecltype(e)表示的类型定义为如下:

--如果e是未加括号的id表达式或未加括号的类成员访问(5.2.5),decltype(e)是类型由CCD_ 27命名的实体。如果没有这样的实体,或者如果e命名了函数集过载,程序格式错误;

--否则,如果e是x值,则decltype(e)T&&,其中Te的类型;

--否则,如果e是左值,则decltype(e)T&,其中T是类型CCD_ 38;

--否则,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&

结束示例][注意:确定涉及的类型的规则7.1.6.4中规定了decltype(auto)--尾注]

decltype(auto)的规则对于初始化表达式的RHS中的额外括号具有类似的含义。下面是C++常见问题这个相关问答的一个例子;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,第二个返回作为对局部变量str的引用的string &

防止预处理器宏相关错误

预处理器宏在与C++语言的交互中有很多微妙之处,其中最常见的在下面列出

  • 在宏定义#define TIMES(A, B) (A) * (B);内的宏参数周围使用括号,以避免不需要的运算符优先级(例如,在TIMES(1 + 2, 2 + 1)中,它产生9,但如果没有(A)(B)周围的括号,则会产生6
  • 在内部有逗号的宏参数周围使用括号:否则将不会编译的assert((std::is_same<int, int>::value));
  • 在函数周围使用括号,以防止包含标头中的宏扩展:(min)(a, b)(同时禁用ADL会产生不必要的副作用)

通常,在编程语言中,"额外"括号意味着它们而不是更改语法解析顺序或含义。添加它们是为了澄清顺序(运算符优先级),以利于阅读代码的人,它们的唯一效果是稍微减缓编译过程,减少理解代码时的人为错误(可能会加快整个开发过程)。

如果一组括号实际上改变了表达式的解析方式,那么根据定义,它们是而不是多余的。将非法/无效解析转换为合法解析的括号并不是"额外的",尽管可能会指出糟糕的语言设计。