3

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?

Igor G
  • 1,838
  • 6
  • 18

2 Answers2

4

This seems to be clarified in the C++23 draft, [class.qual]/1:

In a lookup for a qualified name N whose lookup context is a class C in which function names are not ignored,

  • if the search finds the injected-class-name of C ([class.pre]), or
  • if N is dependent and is the terminal name of a using-declarator ([namespace.udecl]) that names a constructor,

N is instead considered to name the constructor of class C. Such a constructor name shall be used only in the declarator-id of a (friend) declaration of a constructor or in a using-declaration.

In the newfangled C++23 name lookup terminology (which was introduced by P1787R6, "Declarations and where to find them), the name Derived in x.Derived() is considered to be a type of qualified name. And Derived has an injected-class-name, which behaves like a member of Derived for name lookup purposes, so that does hide Base::Derived. The lookup finds the injected-class-name, so Derived is considered to name the constructor of Derived, and the program is ill-formed because this name referring to the constructor is being used neither to declare a friend nor in a using-declaration (i.e., to inherit constructors from a base class), which are the only allowed uses.

Brian Bi
  • 111,498
  • 10
  • 176
  • 312
3

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.

user17732522
  • 53,019
  • 2
  • 56
  • 105