提问人:TemplateRex 提问时间:6/9/2014 最后编辑:CommunityTemplateRex 更新时间:7/11/2014 访问量:9614
除了运算符优先级之外,额外的括号何时起作用?
When do extra parentheses have an effect, other than on operator precedence?
问:
C++ 中的括号用于许多地方:例如,在函数调用和分组表达式中以覆盖运算符优先级。除了非法的额外括号(例如函数调用参数列表周围)之外,C++ 的一般(但不是绝对)规则是额外括号永远不会受到伤害:
5.1 主表达式 [expr.prim]
5.1.1 常规 [expr.prim.general]
6 带括号的表达式是主表达式,其类型和 值与包含表达式的值相同。存在感 括号不影响表达式是否为左值。 括号内的表达式可以在完全相同的上下文中使用 作为那些可以使用封闭表达式的地方,并且具有相同的 含义,除非另有说明。
问题:除了覆盖基本运算符优先级之外,在哪些上下文中,额外的括号会更改 C++ 程序的含义?
注意:我认为将指针到成员语法限制为不带括号超出了范围,因为它限制了语法,而不是允许两种具有不同含义的语法。同样,在预处理器宏定义中使用括号也可以防止不必要的运算符优先级。&qualified-id
答:
TL;博士
额外的括号在以下上下文中更改 C++ 程序的含义:
- 防止与参数相关的名称查找
- 在列表上下文中启用逗号运算符
- 烦恼解析的歧义解决
- 推导表达式中的引用性
decltype
- 防止预处理器宏错误
防止与参数相关的名称查找
如该标准的附录 A 中详述的那样,形式的 a 是 ,但不是 ,因此不是 。这意味着与传统形式相比,在表单的函数调用中阻止了与参数相关的名称查找。post-fix expression
(expression)
primary expression
id-expression
unqualified-id
(fun)(arg)
fun(arg)
3.4.2 与参数相关的名称查找 [basic.lookup.argdep]
1 当函数调用 (5.2.2) 中的 postfix-expression 是 unqualified-id,其他命名空间在平时不考虑 可以搜索非限定查找 (3.4.1),并且在这些命名空间中, 命名空间范围的友元函数或函数模板声明 (11.3) 可能以其他方式不可见。对 搜索取决于参数的类型(以及模板模板) arguments,模板参数的命名空间)。[ 示例:
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 中提到的 cast 和声明也可能在上下文中发生 的声明。在这种情况下,选择是在函数之间 在参数周围使用一组冗余括号的声明 name 和一个对象声明,其中函数样式强制转换为 初始 化。就像 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>
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
decltype(e)
decltype((e))
7.1.6.2 简单类型说明符 [dcl.type.simple]
4 对于表达式,
由 decltype(e)
表示的类型定义为 遵循:e
— if 是带括号的 id-expression 或 不带括号的类成员访问 (5.2.5) 是类型 的实体。如果没有这样的实体,或者如果将 集重载函数,程序格式错误;
e
decltype(e)
e
e
— 否则, if 是 xvalue,则为 ,其中 是
e
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&
—结束示例] [ 注:7.1.6.4 中指定了确定涉及类型的规则。
decltype(auto)
对于初始化表达式的 RHS 中的额外括号,规则具有类似的含义。下面是 C++FAQ 和相关问答中的一个示例decltype(auto)
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++ 语言本身的交互中存在许多微妙之处,下面列出了最常见的
- 在宏定义中的宏参数周围使用括号,以避免不必要的运算符优先级(例如,其中产生 9,但如果没有括号,则会产生 6 和
#define TIMES(A, B) (A) * (B);
TIMES(1 + 2, 2 + 1)
(A)
(B)
- 在包含逗号的宏参数周围使用括号:否则将无法编译
assert((std::is_same<int, int>::value));
- 在函数周围使用括号来防止包含的标头中的宏扩展:(具有禁用 ADL 的不良副作用)
(min)(a, b)
评论
if
while
if (a = b)
==
if ((a = b))
(min)(a, b)
min(A, B)
通常,在编程语言中,“额外”括号意味着它们不会更改语法解析顺序或含义。添加它们是为了澄清顺序(运算符优先级),以便人们阅读代码,它们的唯一作用是稍微减慢编译过程,并减少理解代码时的人为错误(可能加快整个开发过程)。
如果一组括号实际上改变了表达式的解析方式,那么根据定义,它们不是多余的。将非法/无效解析转换为合法解析的括号不是“额外的”,尽管这可能会指出糟糕的语言设计。
评论
&(C::f)
&
C::f
expr.unary.op/4
&
()
::*