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

时间:2014-06-09 08:56:47

标签: c++ c++11 language-lawyer parentheses c++-faq

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

5.1主要表达式[expr.prim]

5.1.1一般[expr.prim.general]

  

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

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

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

2 个答案:

答案 0 :(得分:108)

TL; DR

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

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

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

如标准的附录A所详述,post-fix expression形式的(expression)primary expression,但不是id-expression,因此不是unqualified-id 1}}。这意味着与传统形式(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
}
  

-end example]

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

逗号运算符在大多数类似列表的上下文(函数和模板参数,初始化列表等)中具有特殊含义。在这种上下文中,a, (b, c), d形式的括号可以使逗号运算符与逗号运算符不适用的常规形式a, b, c, d进行比较。

5.18逗号运算符[expr.comma]

  

2在逗号被赋予特殊含义的上下文中,[例如: in   函数参数列表(5.2.2)和初始值列表   (8.5) -end example]如第5章所述的逗号运算符可以   仅出现在括号中。 [例如:

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

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

烦恼解析的模糊性解析

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

6.8歧义解决[stmt.ambig]

  

1 涉及表达式语句的语法含糊不清   和声明:具有函数样式的表达式语句   显式类型转换(5.2.3),因为它最左边的子表达式可以   与第一个声明者开始的声明无法区分   a(。在这些情况下,声明是声明

8.2歧义解决[dcl.ambig.res]

  

1 函数式之间相似性引起的歧义   在6.8中提到的演员和声明也可以在上下文中出现   声明。在这种情况下,选择是在一个函数之间   声明与参数周围的一组冗余括号   具有函数样式转换的名称和对象声明   初始化。正如6.8中提到的含糊不清一样   决议是考虑任何可能是a的构造   声明声明。 [注意:声明可以明确   由非函数式转换消除歧义,由=表示   初始化或删除周围的冗余括号   参数名称。 - 尾注] [例子:

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
}
  

-end example]

一个着名的例子是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对于表达式edecltype(e) 表示的类型定义为   如下:

     

- 如果e是未加密码的id-expression或者   未加密级的类成员访问(5.2.5),decltype(e)是类型   由e命名的实体。如果没有这样的实体,或者e命名为a   一组重载函数,程序格式不正确;

     

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

     

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

-end example] [注:确定涉及的类型的规则   decltype(auto)在7.1.6.4中指定。 - 后注]

decltype(auto)的规则对初始化表达式的RHS中的额外括号具有相似的含义。以下是C++FAQthis related 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 &,这是对局部变量str的引用。

防止预处理器宏相关错误

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

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

答案 1 :(得分:4)

一般来说,在编程语言中,&#34; extra&#34;括号表示它们更改语法分析顺序或含义。为了使人们阅读代码的好处,它们被添加以澄清顺序(运算符优先级),它们的唯一作用是稍微减慢编译过程,并减少理解代码时的人为错误(可能加速整个开发过程) )。

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