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

Viewed 210

Not sure what I am missing, but why wont this compile?

class Game
{
};

class Actor
{
   Game* mGame;
   Game* Game() { return mGame; }
};

int main()
{
  Actor a();

  return 0;
}

g++ -std=c++17 main.cpp -o test

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
      |      

Obviously, if I change the function to GetGame, no problems. Just wondering why - what am I missing?

Thanks!

3 Answers

Since the language lawyer tag has been added, and there appears to be some confusion as to whether this is defined by the standard or not, here's what the standard has to say to complement the existing answers.

In [basic.scope.hiding] :

If a class name (11.2) or enumeration name (9.6) and a variable, data member, function, or enumerator are declared in the same declarative region (in any order) with the same name (excluding declarations made visible via using-directives (6.4.1)), the class or enumeration name is hidden wherever the variable, data member, function, or enumerator name is visible.

Furthermore, in [class.name] we find :

If a class name is declared in a scope where a variable, function, or enumerator of the same name is also declared, then when both declarations are in scope, the class can be referred to only using an elaborated-type-specifier (6.4.4). [Example:

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()
}

— end example]

So, in the scope of f, stat refers to the function of that name, whereas struct stat (elaborated type specifier) refers to the class.

Or in the example from the OP, in the scope of Actor, Game refers to the (member) function of that name, whereas class Game (elaborated type specifier) refers to the class.

Note that alternatively, ::Game can be used to refer to the class.

Finally, a bit further down in [class.name], there's a relevant quote about writing such code :

4 [Note: The declaration of a class name takes effect immediately after the identifier is seen in the class definition or elaborated-type-specifier. For example, class A * A; first specifies A to be the name of a class and then redefines it as the name of a pointer to an object of that class. This means that the elaborated form class A must be used to refer to the class. Such artistry with names can be confusing and is best avoided. — end note]

So not only does the standard fully cover this scenario - it also recommends against writing such confusing code.

Consider the following:

class Game
{
};

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

Sure, this can be resolved using ::Game() vs this->Game(), but the default is ambiguous because the standard doesn't state which to prefer in the case of symbol-name reuse. Therefore, it will not compile due to ambiguity.

With your code the way it currently is, I tested it on Compiler Explorer using various compilers...

Here are the results of 3 different compilers, their generated assembly, and possible errors or warnings. All compiler options are set to -std=c++17 -O3.


x86-64 clang(trunk)

asm

main:                                   # @main
        xor     eax, eax
        ret

Warnings-Errors

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

where a(); is highlighted as the warning in question


x86-64 gcc (trunk)

Fails to compile!

Warnings-Errors

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

where Game() is highlighted as the error in question


x64 msvc v19.24

asm

main    PROC
        xor     eax, eax
        ret     0
main    ENDP

Warnings-Errors

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

where Actor a(); is highlighted as the warning in question



These are the results based on your current code. Now let's look at these 3 same examples but modifying just one little piece of your code...

I'm going to change Actor a(); in your main function to Actor a{}; instead and let's look at the differences...



x86-64 clang(trunk)

asm

main:                                   # @main
        xor     eax, eax
        ret

Warnings-Errors

None!


x86-64 gcc (trunk)

Fails to compile!

Warnings-Errors

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

where Game() is highlighted as the error in question.


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

Warnings-Errors

None!



Assessment

If you compare the 3 you will see that both Clang and MSVC will compile in both situations, however, they will both give a warning for the first case and will compile without error for the second. GCC, on the other hand, fails to compile in both cases and generates the same exact warning or compiler error.

In the first case which is your original source, Clang and MSVC are able to determine that you meant to call the constructor of said class Actor but generates a warning message where GCC fails to compile it all together while using the () operator. GCC can not determine if you meant to call Actor::Game() or Game::Game().

We can also reason that both Clang and MSVC are pointing their warnings from the first case at a() while GCC is pointing its error at Game() in both cases.

To inspect what is going on even more, in the first case, both Clang and MSVC are generating almost identical assembly. In the second case, Clang is still generating the same assembly, but MSVCfootprint tells a completely different story!

What I can say from my current knowledge without doing any more research, based on the comparisons between

Actor a();

and

Actor a{};

and the behavior that I'm seeing from the generated warnings, errors, and assembly code is this...

I believe that it might be Implementation Defined across the different compilers.

I can not say if this is UB or not, but I would probably suspect that it could be in some cases in which you can see from the generated assembly, warnings, and errors above...

If you are using the () operator for the class's constructor, then there is at least some ambiguity within all three compilers. GCC doesn't even compile and generates an error. Clang and MSVC both compile where Clang gives the warning that the empty parenthesis is interpreted as a function declaration and MSVC gives the warning that the prototyped function was not called.

Yet when we switch to the {} operator or initializer list... Clang generates the same assembly that it did before but no longer generates a warning. MSVC gives a thorough set of instructions to where it appears that both classes are being constructed and generates no warning. GCC craps out in both situations and says, "I give up on your intentions" here's my error message always pointing at Game()!



Conclusion

So to answer your question:

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

Depending on the context of use and the compiler that is being used it could be the same, and for other compilers, they may or will reject it.

So who's to say which compiler's interpretation is accurate and correct... I don't have a copy of the standard so I can not dive any further into the language-lawyer part of this, but I can generalize what is happening and why it is happening as I have just demonstrated.

I hope that this reasoning might help you within the naming conventions and how the compilers try to interpret such names and symbols while trying to turn them into objects.

So there may be some ambiguity in some contexts and no ambiguity in others. This leads me to believe this is implementation-defined across compilers since AFAIK the standard doesn't require a specific notation of name-reuse and that this could lead to UB! Therefore, this would not be good practice and definitely a code smell!

And all of this is agnostic of the hardware architecture too!

Related