g++ 拒绝,clang++ 接受: foo(x)(“bar")(“baz");

2022-01-23 00:00:00 syntax g++ c++ clang++ most-vexing-parse

前几天有人问为什么有些东西可以用clang编译,但不能用gcc编译.我直观地理解正在发生的事情并能够帮助这个人,但这让我想知道――根据标准,哪个编译器是正确的?这是代码的简化版本:

Somebody had asked the other day why something compiles with clang, but not with gcc. I intuitively understood what was happening and was able to help the person, but it got me wondering -- according to the standard, which compiler was correct? Here is a boiled down version of the code:

#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++ 给出以下错误:

This compiles fine with clang++, but g++ gives the following error:

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++ 很高兴:

If I add a pair of parentheses in line 21, g++ is happy:

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

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

In other words, g++ interprets this line as:

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

我认为它是 g++ 中的错误,但我想再次询问标准专家,哪个编译器出错了?

Methinks itsa bug in g++, but again, I wanted to ask the standard experts, which compiler got it wrong?

PS:gcc-4.8.3、clang-3.5.1

PS: gcc-4.8.3, clang-3.5.1

推荐答案

据我所知,这在草案 C++ 标准部分 6.8 Ambiguity resolution 中有所涉及表示表达式语句和声明之间可能存在歧义并说:

As far as I can tell this is covered in the draft C++ standard section 6.8 Ambiguity resolution which says that there can be an ambiguity between expression statements and declarations and says:

涉及表达式语句的语法存在歧义和声明:具有函数样式的表达式语句显式类型转换(5.2.3)作为其最左边的子表达式可以是与第一个声明符开始的声明没有区别带有 (.在这些情况下,该语句是一个声明. [注意:要消除歧义,可能需要检查整个陈述以确定它是表达式语句还是声明.这消除许多例子的歧义.[ 示例:假设 T 是简单类型说明符(7.1.6),

There is an ambiguity in the grammar involving expression-statements and declarations: An expression statement with a function-style explicit type conversion (5.2.3) as its leftmost subexpression can be indistinguishable from a declaration where the first declarator starts with a (. In those cases the statement is a declaration. [ Note: To disambiguate, the whole statement might have to be examined to determine if it is an expression-statement or a declaration. This disambiguates many examples. [ Example: assuming T is 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

然后说:

其余情况是声明.[ 例子:

The remaining cases are declarations. [ Example:

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 将是正确的.

It seems like this case falls into the declaration examples in particular the last example seems to make the case in the OP, so gcc would be correct then.

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

Relevant section mentioned above 5.2.3 Explicit type conversion (functional notation) says:

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

[...] If the type specified is a class type, the class type shall be complete. If the expression list specifies more than a single value, the type shall be a class with a suitably declared constructor (8.5, 12.1), and the expression T(x1, x2, ...) is equivalent in effect to the declaration T t(x1, x2, ...); for some invented temporary variable t, with the result being the value of t as a prvalue.

8.3 声明符的含义其中说:

在声明 T D 中,其中 D 具有以下形式

In a declaration T D where D has the form

( D1 ) 

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

the type of the contained declarator-id is the same as that of the contained declarator-id in the declaration

T D1

括号不会改变嵌入的 declarator-id 的类型,但是它们可以改变复杂声明符的绑定.

Parentheses do not alter the type of the embedded declarator-id, but they can alter the binding of complex declarators.

更新

我最初使用的是 N337 但如果我们看在 N4296 部分 6.8 已更新,现在包括以下说明:

I was originally using N337 but if we look at N4296 section 6.8 was updated an it now includes the following note:

如果语句在语法上不能是声明,则没有歧义,因此该规则不申请.

If the statement cannot syntactically be a declaration, there is no ambiguity, so this rule does not apply.

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

which means that gcc is incorrect since:

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

不能是一个有效的声明,我最初将第 2 段解释为如果你的 case 以以下任何一个开头,那么它就是声明,这可能是 gcc 实现者也被解释.

can not be a valid declaration, I originally interpreted paragraph 2 as saying if you case begins with any of the following then it is declaration, which is perhaps how the gcc implementor interpreted as well.

我应该对第 2 段更加怀疑,因为第 2 段的唯一规范部分确实没有提及第 1 段和似乎对一个不规范的例子提出了要求.我们可以看到,段落 2 中的语句现在实际上是一个更有意义的注释.

I should have been more suspicious of paragraph 2 since the only normative part of paragraph 2 really said nothing with respect to paragraph 1 and seems to place a requirement on an example which is not normative. We can see that that statement form paragraph 2 is now actually a note which makes much more sense.

作为 T.C.如下所述,第 2 段实际上从来都不是规范的,它只是以这种方式出现,他 链接到更改修复了它.

As T.C. noted below, paragraph 2 was actually never normative, it just appeared that way and he linked to the change that fixed it.

相关文章