Naming a method after the class
Consider this class:
struct B { B() {} int B() { return 42; } // FAIL! };
This code is illegal because a method name can’t be named after the class name. I was wondering if there’s a particular reason for this, since it’s a restriction you can easily overcome by writing:
struct A { A() : b() {} int B() { return 42; } }; using B = A; int main() { auto b = B(); // construction cout << b.B(); // method invokation }
So, from the user perspective, with non-static methods, there’s no such restriction. Indeed, you can use B both to declare a variable of the type and call a method on an instance of the class B.
Even with static methods, things doesn’t change. You’re still calling the constructor with the syntax B() and the static method with B::B().
struct A { A() : b() {} static int B() { return 42; } }; using B = A; int main() { auto b = B(); // construction cout << B::B(); // static method invokation }
In both cases there’s no confusion between construction and method invocation.
So, why is it forbidden?
The first problem would appear clearly when you try to call the constructor or the method from within the class, i.e.:
struct B { B() {} int B() { return 42; } auto F() { return B(); } // B or int? };
So, the real problem is how to differentiate a constructor call from a method call from within the class, when the parameters are the same (empty, in our example).
Proposal 1: Changing the constructor invocation syntax
One naïve way of overcoming this limitation would be to use the this
keyword to invoke the constructor, and keep the normal name to invoke the function.
struct B { B() {} int B() { return 42; } auto F() { return this(); } // (new syntax) B! auto G() { return B(); } // int! };
Some considerations about this option:
- It’s ugly.
- It’s not backward-compatible, changing the meaning of existing code (but an automated fix is trivial)
- Will differentiate calling syntax for a constructor inside the class declaration and outside the class.
- It’s difficult to read
- Did I mention it’s ugly?
Consider that, on the other hand, the following code: it is perfectly correct, and the standard says exactly which C()
to be picked.
struct B { struct C{}; int C() { return 42; } auto F() { return C(); } // C or int? };
The solution is in basic.scope.hiding, which, among the rest, says:
If a class or enumeration name and an object, function, or enumerator are declared in the same scope (in any order) with the same name, the class or enumeration name is hidden wherever the object, function, or enumerator name is visible.
Proposal 2: Changing the method invocation syntax
Let’s try the other way round, changing the syntax for function invocation.
class B { B() {} int B() { return 42; } auto F() { return B(); } // B! auto G() { return this->B(); } // (not-so-new syntax) int! };
This is clear, and the meaning is obvious to any C++ programmer! And it’s backward-compatible, too. Looks like this is the perfect solution for constructor<->method name conflict resolution.
What about static methods?
class B { B() {} static int B() { return 42; } auto F() { return B(); } // B! auto G() { return B::B(); } // (not-so-new syntax) int! };
Apparently, this is still going to work, and uses the same syntax as anyone would use from outside the class.
Where to change the standard?
I think the only problem right now is that a constructor doesn’t hide a function in the same scope.
The key, in my opinion, is somewhere around in basic.scope.hiding. Right now (C++14) declaring a method and a constructor with the same name is a syntax error because one doesn’t hide the other.
Making the constructor hide the function is a starting point, but I don’t think it’s the full story.
Some more random thoughts
Obviously, there’s more than this. Here’s the first consideration that came to my mind while pondering the proposed solution.
- If we add a method with a signature different from any of the constructors, should we still force to call it with the syntax from Proposal 2?
- Yes. By not doing it, we would incur in a silent change of the program’s semantics later, when we decide to add a new constructor with the same signature.
So, that’s all?
I don’t know, really.
I tried to find other corner cases where this language extension would fail miserably, but couldn’t find any. Can you help me?
Please post in the comments any worst case that comes to your mind, and let’s check if this proposal still holds. In, say, two-three months, if the idea still holds, I’d like to see it transformed into a proposal for the C++ committee (and, maybe, being part of it).