为什么函数名不能与返回名类型相同?

Why can't function name be the same as return name type?

本文关键字:类型 函数 不能 为什么 返回      更新时间:2023-10-16

不确定我缺少了什么,但为什么不编译?

class Game
{
};
class Actor
{
Game* mGame;
Game* Game() { return mGame; }
};
int main()
{
Actor a();
return 0;
}

g++-std=c++17 main.cpp-o测试

main.cpp:8:10: error: declaration of ‘Game* Actor::Game()’ changes meaning of ‘Game’ [-fpermissive]
8 |    Game* Game() { return mGame; }
|          ^~~~
main.cpp:1:7: note: ‘Game’ declared here as ‘class Game’
1 | class Game
|      

显然,如果我将函数更改为GetGame,没有问题。只是想知道为什么——我错过了什么?

谢谢!

由于添加了语言律师标签,并且对于这是否由标准定义似乎存在一些困惑,因此标准必须说明以下内容以补充现有答案。

[basic.scope.hiding]:

如果类名(11.2)或枚举名(9.6。

此外,在[class.name]中,我们发现:

如果在同时声明同名变量、函数或枚举器的作用域中声明类名,则当两个声明都在作用域中时,只能使用详细说明的类型说明符(6.4.4)来引用该类。[示例:

struct stat {
// ...
};
stat gstat;                // use plain stat to define variable
int stat(struct stat*);    // redeclare stat as function
void f() {
struct stat* ps;         // struct prefix needed to name struct stat
stat(ps);                // call stat()
}

--结束示例]

因此,在f的作用域中,stat指的是该名称的函数,而struct stat(详细的类型说明符)指的是类。

或者在OP的例子中,在Actor的范围中,Game指的是该名称的(成员)函数,而class Game(详细的类型说明符)指的是类。

请注意,或者,::Game可以用于引用类。

最后,在[class.name]中,有一句关于编写此类代码的相关名言:

4[注意:类名的声明在类定义或精化类型说明符中看到标识符后立即生效。例如,class A * A;首先将A指定为类的名称,然后将其重新定义为指向该类对象的指针的名称。这意味着必须使用精化形式class A来引用该类。Su带有名字的ch艺术可能会令人困惑,最好避免--尾注]

因此,该标准不仅完全涵盖了这种情况,还建议不要编写这种令人困惑的代码。

考虑以下内容:

class Game
{
};
class Actor
{
Game* mGame;
Game* Game() { return mGame; }
void test() { auto g = Game(); /*constructing game or calling fn and saving ptr result?*/ }
};

当然,这可以使用::Game()this->Game()来解决,但默认值是不明确的,因为标准没有说明在符号名称重用的情况下更喜欢哪个。因此,由于歧义,它不会编译。

使用当前的代码,我在编译器资源管理器上使用各种编译器对其进行了测试。。。

以下是3个不同编译器的结果、它们生成的程序集以及可能的错误或警告。所有编译器选项都设置为-std=c++17 -O3


x86-64 clang(中继)

asm

main:                                   # @main
xor     eax, eax
ret

警告错误

warning: empty parentheses interpreted as a function
declaration [-Wvexing-parse]

其中a();突出显示为有问题的警告


x86-64 gcc(中继)

编译失败!

警告错误

error: declaration of 'Game*' Actor::Game()' changes meaning of
'Game' [-fpermissive]

其中Game()突出显示为问题中的错误


x64 msvc v19.24

asm

main    PROC
xor     eax, eax
ret     0
main    ENDP

警告错误

warning C4930: 'Actor a(void)': prototyped function not called
(was a variable definition intended?)

其中Actor a();突出显示为有问题的警告



这些是基于当前代码的结果。现在让我们看看这3个相同的例子,但只修改了代码的一小部分。。。

我将把main函数中的Actor a();改为Actor a{};,让我们看看它们之间的区别。。。



x86-64 clang(中继)

asm

main:                                   # @main
xor     eax, eax
ret

警告错误

没有!


x86-64 gcc(中继)

编译失败!

警告错误

error: declaration of 'Game*' Actor::Game()' changes meaning of
'Game' [-fpermissive]

其中Game()突出显示为有问题的错误。


x64 msvc v19.24

asm

a$ = 0
main    PROC
$LN3:
push    rdi
sub     rsp, 16
lea     rax, QWORD PTR a$[rsp]
mov     rdi, rax
xor     eax, eax
mov     ecx, 8
rep stosb
xor     eax, eax
add     rsp, 16
pop     rdi
ret     0
main    ENDP

警告错误

没有!



评估

如果比较3,你会发现ClangMSVC在两种情况下都会编译,然而,它们都会在第一种情况下发出警告,在第二种情况下编译时不会出错。另一方面,GCC在这两种情况下都无法编译,并生成完全相同的警告或编译器错误。

在第一种情况下,它是您的原始源,ClangMSVC能够确定您打算调用所述类Actor的构造函数,但会生成一条警告消息,其中GCC在使用()运算符时未能将其全部编译在一起。GCC无法确定您是要呼叫Actor::Game()还是Game::Game()

我们还可以推断出,ClangMSVC都将其来自第一种情况的警告指向a(),而GCC在这两种情况下都将其错误指向Game()

为了进一步检查正在发生的事情,在第一种情况下,ClangMSVC都生成了几乎相同的程序集。在第二种情况下,Clang仍然生成相同的程序集,但MSVC足迹告诉了一个完全不同的故事!

根据之间的比较,我可以在不做任何研究的情况下,根据我目前的知识说些什么

Actor a();

Actor a{};

我从生成的警告、错误和汇编代码中看到的行为是。。。

我相信在不同的编译器中可能是Implementation Defined

我不能说这是否是UB,但我可能怀疑在某些情况下,你可以从上面生成的程序集、警告和错误中看到。。。

如果对类的构造函数使用()运算符,那么这三个编译器中至少都存在一些歧义。GCC甚至不编译并生成错误。ClangMSVC都进行编译,其中Clang给出空括号被解释为函数声明的警告,而MSVC给出原型函数未被调用的警告。

然而,当我们切换到{}运算符或初始值设定项列表时。。。Clang生成与以前相同的程序集,但不再生成警告。MSVC给出了一组完整的instructions,其中两个类似乎都在构建中,并且没有生成任何警告。GCC在这两种情况下都会崩溃,并说:"我放弃了你的意图"这是我的错误消息,总是指向Game()



结论

因此,为了回答您的问题:

为什么函数名称不能与返回名称类型相同?

根据使用的上下文和正在使用的编译器,它可能是相同的,对于其他编译器,他们可能会拒绝它。

那么谁能说哪个编译器的解释是准确和正确的呢。。。我没有standard的副本,所以我不能再深入研究其中的language-lawyer部分,但我可以概括正在发生的事情以及为什么会发生,正如我刚刚演示的那样。

我希望这种推理可以帮助您理解命名约定,以及编译器如何在将这些名称和符号转换为对象的同时尝试解释它们。

因此,在某些情况下可能存在一些歧义,而在其他情况下则没有歧义。这让我相信这是跨编译器定义的实现,因为AFAIK标准不需要name-reuse的特定符号,这可能会导致UB!因此,这将不是一个好的做法,而且绝对是一种代码气味!

所有这些都与硬件架构无关!