c++constructorc++17language-lawyername-lookup

Do constructors of class T hide methods with the same name T in base class?


I'm trying to figure out why major compilers refuse to compile the following example (godbolt):

struct Base
{
    void Derived();
};

struct Derived : public Base
{
};

int main()
{
    Derived     x;

    x.Derived();    // Compilation error.
                    // gcc:   Invalid use of Derived::Derived.
                    // clang: Cannot refer to type member 'Derived' in 'Derived' with '.'

    return 0;
}

My question is: what clause in the C++ standard makes the above example ill-formed?

Intuition suggests that the error may have something to do with implicitly declared default constructor Derived::Derived(). But then, the standard seems pretty clear on this:

[class.ctor]
Constructors do not have names. [...]
Because constructors do not have names, they are never found during unqualified name lookup [...]

My interpretation of that clause is that since constructors don't have names, they can't hide any name in any base class. So, unqualified name Derived in expression x.Derived should be resolved to x.Base::Derived, which seems perfectly callable.

What am I missing or getting wrong?


Solution

  • Each class binds the name of the class itself in its own scope (naming the class type or its constructor depending on context, but the latter only in qualified names). That's called the injected-class-name. (See [class.pre]/2, [class.member.lookup]/3 and [class.qual]/2).

    In x.Derived(), because x is of type Derived, the scope of Derived is searched before that of the Base base class. The injected-class-name of Derived is found in this scope and so lookup doesn't proceed to the base class. (See [basic.lookup.classref]/2 and [class.member.lookup]/4.)

    It is not clear to me whether the injected-class-name is supposed to be considered a member of the class (or one of its base classes) for the purpose of [expr.ref]/4. Otherwise that sentence would forbid it on the right-hand side of ..

    However, there is a non-normative note directly under that sentence claiming that the injected-class-name would be permitted as a nested member and [basic.lookup.general]/3 makes it a member "for the purposes of name hiding and lookup". If that is the case I suppose [expr.ref]/6.4 would forbid it instead for naming a "nested type". Not sure about this detail. It has also been reworked in the C++23 draft.