g ++ rejects,clang ++接受:foo(x)(“bar”)(“baz”);

时间:2015-02-02 17:22:54

标签: c++ syntax g++ clang++ most-vexing-parse

有一天有人asked为什么有些东西会用clang编译,而不是用gcc编译。我直观地理解了发生了什么,并且能够帮助这个人,但它让我想知道 - 根据标准,哪个编译器是正确的?这是代码的简化版本:

#include <iostream>
#include <string>

class foo
{
public:
    foo(const std::string& x):
        name(x)
    { }
    foo& operator()(const std::string& x)
    {
        std::cout << name << ": " << x << std::endl;
        return (*this);
    }
    std::string name;
};

int main()
{
    std::string x = "foo";
    foo(x)("bar")("baz");
    return 0;
}

使用clang ++可以很好地编译,但是g ++会出现以下错误:

runme.cpp: In function ‘int main()’:
runme.cpp:21:11: error: conflicting declaration ‘foo x’
    foo(x)("bar")("baz");
        ^
runme.cpp:20:17: error: ‘x’ has a previous declaration as ‘std::string x’
    std::string x = "foo";

如果我在第21行添加一对括号,g ++很高兴:

(foo(x))("bar")("baz");

换句话说,g ++将此行解释为:

foo x ("bar")("baz");

在g ++中解决它的错误,但是我又想问标准专家,哪个编译器弄错了?

PS:gcc-4.8.3,clang-3.5.1

2 个答案:

答案 0 :(得分:17)

据我所知,C ++标准部分6.8 歧义解决方案草案中涵盖了这一点,它表明表达式声明和声明之间可能存在歧义,并说:

  

涉及表达式语句的语法含糊不清   和声明:具有函数样式的表达式语句   显式类型转换(5.2.3),因为它最左边的子表达式可以   与第一个声明者开始的声明无法区分   (a。在这些情况下,声明是声明。[注意:To   消除歧义,整个声明可能需要进行审查   确定它是表达式语句还是声明。这个   消除许多例子的歧义。 [例子:假设T是a   simple-type-specifier(7.1.6),

并给出以下示例:

T(a)->m = 7; // expression-statement
T(a)++; // expression-statement
T(a,5)<<c; // expression-statement

T(*d)(int); // declaration
T(e)[5]; // declaration
T(f) = { 1, 2 }; // declaration
T(*g)(double(3)); // declaration

然后说:

  

其余案件是声明。 [例如:

class T {
    // ...
   public:
    T();
    T(int);
    T(int, int);
};
T(a); // declaration
T(*b)(); // declaration
T(c)=7; // declaration
T(d),e,f=3; // declaration
extern int h;
T(g)(h,2); // declaration
     

-end example] -end note]

似乎这个案例属于声明示例,特别是最后一个例子似乎在OP中提出了这个案例,所以gcc就是正确的。

上面提到的相关部分5.2.3 显式类型转换(功能表示法)说:

  

[...]如果指定的类型是类类型,则类类型应完整。如果表达   list指定多个值,该类型应为具有适当声明的构造函数的类(8.5,12.1),   表达式T(x1,x2,...)与声明T t(x1,x2,...)有效;对于一些   发明了临时变量t,结果是t作为prvalue的值。

8.3 声明者的含义,其中包含:

  

在声明T D中,D的格式为

( D1 ) 
     

包含的declarator-id的类型与   声明中包含声明者id

T D1
     

括号不会改变嵌入式声明符id的类型,但是   他们可以改变复杂声明者的绑定。

更新

我最初使用N337但如果我们查看N4296部分6.8已更新,则现在包含以下注释:

  

如果语句在语法上不能成为声明,则不存在歧义,因此该规则不会   应用

这意味着gcc不正确,因为:

foo x ("bar")("baz");

不能是一个有效的声明,我最初解释段落2如果你的案例以下列任何一个开头那么它就是声明,这也许是gcc实现者​​如何解释的。

我应该更加怀疑第2段,因为第2段的唯一规范部分对第1段没有任何说明,似乎对一个例子提出了要求。不是规范性的。我们可以看到段落2中的陈述现在实际上是一个更有意义的说明。

作为T.C.如下所述,第2段实际上从来都不是规范性的,只是出现了这种方式而且linked to the change that fixed it

答案 1 :(得分:5)

如果我们删除该行

std::string x = "foo";

然后g ++抱怨:

foo(x)("bar")("baz");

语法错误:

foo.cc:20:18: error: expected ',' or ';' before '(' token
     foo(x)("bar")("baz");

我不知道foo (x)("bar")("baz");如何成为有效的声明,显然g ++也不能。第foo x("bar")("baz");行被拒绝并出现同样的错误。

Shafik的帖子中提到的“模糊性解决方案”仅在表达式语句在语法上与声明无法区分时启动。但是在这种情况下,它不是一个有效的声明语法,因此没有歧义,它必须是一个表达式语句。

g ++无法将该行作为表达式语句处理,因此它是一个g ++错误。

这与最近在SO上讨论的this g++ bug非常类似;似乎g ++可能在处理过程中决定该行必须是声明。