什么时候额外的括号有效,除了运算符优先级?

Tem*_*Rex 91 c++ c++-faq parentheses language-lawyer c++11

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

5.1主要表达式[expr.prim]

5.1.1一般[expr.prim.general]

6带括号的表达式是一个主表达式,其类型和值与所附表达式的类型和值相同.括号的存在不会影响表达式是否为左值.除非另有说明,否则带括号的表达式可以在与可以使用所包含的表达式的上下文完全相同的上下文中使用,并且具有相同的含义.

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

注意:我认为没有括号的指针到成员语法的限制&qualified-id超出了范围,因为它限制了语法而不是允许两个具有不同含义的语法.同样,在预处理程序宏定义中使用括号也可以防止不必要的运算符优先级.

Tem*_*Rex 110

TL; DR

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

  • 防止依赖于参数的名称查找
  • 在列表上下文中启用逗号运算符
  • 困惑解决烦恼的解析
  • 推断decltype表达式中的参考性
  • 防止预处理器宏错误

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

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

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

1当函数调用(5.2.2)中的postfix-expression是非限定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
}
Run Code Online (Sandbox Code Playgroud)

- 末端的例子]

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

逗号运算符在大多数类似列表的上下文(函数和模板参数,初始化列表等)中具有特殊含义.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);
Run Code Online (Sandbox Code Playgroud)

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

令人烦恼的解析模糊解析

向后兼容C及其神秘的函数声明语法可能导致令人惊讶的解析歧义,称为烦恼的解析.从本质上讲,任何可以解析为声明的内容都将被解析为一个,即使竞争解析也适用.

6.8歧义解决[stmt.ambig]

1 语法中涉及表达式语句和声明存在歧义:具有函数式显式类型转换(5.2.3)的表达式语句,因为其最左侧的子表达式与第一个声明符以a开头的声明无法区分( .在这种情况下该语句是一个声明.

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
}
Run Code Online (Sandbox Code Playgroud)

- 末端的例子]

一个着名的例子是令人兴奋的解析,这是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
Run Code Online (Sandbox Code Playgroud)

这声明了一个函数,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
Run Code Online (Sandbox Code Playgroud)

C++ 11具有大括号初始化器语法,允许在许多上下文中支持这种解析问题.

decltype表达式中推导出参考性

auto类型推导相反,decltype允许推导出参考(左值和右值参考).规则区分decltype(e)decltype((e))表达:

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

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

- 如果e是未加密码的id-expression或未加括号的类成员访问(5.2.5),decltype(e)则是由名为的实体的类型e.如果没有这样的实体,或者e命名一组重载函数,程序就会形成错误;

- 否则,如果e是xvalue,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&
Run Code Online (Sandbox Code Playgroud)

-end example] [注:确定涉及类型的规则 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
Run Code Online (Sandbox Code Playgroud)

第一个返回string,第二个返回string &,这是对局部变量的引用str.

防止预处理器宏相关错误

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

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

  • 并不真正改变程序的含义,而是最佳实践并影响编译器发出的警告:如果表达式是赋值,则应在`if` /`while`中使用额外的括号.例如`if(a = b)` - 警告(你的意思是`==`?),而`if((a = b))` - 没有警告. (7认同)
  • @JamesKanze:注意OP和TemplateRex是同一个人^ _ ^ (5认同)