TL; DR
在以下情况下,多余的括号会更改C ++程序的含义:
- 防止依赖于参数的名称查找
- 在列表上下文中启用逗号运算符
- 令人烦恼的解析度的歧义解决
- 推论
decltype
表达的参考性
- 防止预处理器宏错误
防止依赖于参数的名称查找
正如该标准的附录A所详细说明post-fix expression
的,形式(expression)
的a是a primary expression
,但不是an id-expression
,因此不是an 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)。对搜索的这些修改取决于自变量的类型(对于模板模板自变量,则为模板自变量的名称空间)。[示例:
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
}
—结束示例]
最著名的例子是“ 最令人烦恼的解析”,这是斯科特·迈耶斯(Scott Meyers)在他的有效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对于表达式e
,用表示的类型decltype(e)
定义如下:
—如果e
是未括号化的id表达式或未括号化的类成员访问权限(5.2.5),decltype(e)
则是由命名的实体的类型e
。如果没有这样的实体,或者e
命名一组重载函数,则程序格式错误;
—否则,如果e
是xvalue,decltype(e)
则是T&&
,其中T
的类型e
;
—否则,如果e
是左值,decltype(e)
则是T&
,其中T
的类型e
;
—否则decltype(e)
为的类型e
。
十进制类型说明符的操作数是未求值的操作数(第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)
初始化表达式的RHS中的额外括号具有相似的含义。这是C ++ FAQ和相关问答中的一个示例
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但在没有括号(A)
和的情况下会产生6的优先级)(B)
- 在内部带有逗号的宏参数周围使用括号:
assert((std::is_same<int, int>::value));
否则将无法编译
- 在函数周围使用圆括号来防止包含的标头中的宏扩展:(
(min)(a, b)
具有也会禁用ADL的有害副作用)
&(C::f)
,的操作数&
仍为C::f
,不是吗?