"hidden overloaded virtual function" with more than 2 classes

2.5k views Asked by At

Minimal example:

class A {};
class B : public virtual A {};
class C : public virtual B {};

// define two overloading virtual functions
// no inheritance, so no overriding/hiding in this class
struct visitor1
{
    virtual void visit(A& ) {}
    virtual void visit(B& ) {}
    virtual ~visitor1();
};

// covariant types are not allowed for overriding virtuals,
// so the C-visit function would hide A and B, so we add them
// using the "using" keyword
struct visitor2 : public visitor1
{
    using visitor1::visit;
    virtual void visit(C& ) {}
    virtual ~visitor2();
};

// the B-visit is a correct override
// without the use of "using" (or the C-visit) below, the
// compiler warns that the C-visit function from visitor3
// is being overridden
struct visitor3 final : public visitor2
{
    //using visitor2::visit;
    //void visit(C &) override {}
    void visit(B &) override {}
};

Compiler:

$ clang++ -Wall -Wextra -Weverything -Wno-c++98-compat visitor.cpp 
visitor.cpp:32:14: warning: 'visitor3::visit' hides overloaded virtual function [-Woverloaded-virtual]
        void visit(B &) override {}
             ^
visitor.cpp:20:22: note: hidden overloaded virtual function 'visitor2::visit' declared here: type mismatch at 1st parameter ('C &' vs 'B &')
        virtual void visit(C& ) {}
                     ^

Question (assuming the commented lines from visitor3 stay commented):

  • Why does the compiler complain that the visitor3::visit(B&) is hiding visitor2::visit(C&)?
  • Why does it not complain that visitor3::visit(B&) is hiding visitor1::visit(A&) (what's different here)?

Additional question: How do I achieve that both visitor2::visit(C&) and visitor1::visit(A&) are not hidden in visitor3? Does using visitor2::visit; in visitor3 suffice?

2

There are 2 answers

4
Oliv On BEST ANSWER

The declaration of void visit(B&) or any declaration a member named visit (it could have been a data member int visit;) in visitor3 hides any member of a parent called visit.

Here the warning of the compiler is more a goodies. It recognized a common mistake pattern which consists in overriding a base virtual member function without taking care over of overloads. But it does not detect all hidden names, or does not warn for all of them. Whatsoever, all visit from visitor1 and visitor2 are hidden.

Additional answer: The using declaration that name members of a base class is a declaration inside the derived class of all the members in the base class that have that name and are visible. So this effectively include members that where also declared by a using declaration in the base class.

(These members declared by a using declaration behave closely as if they were actual members first declared in that class. Even overload resolution considers their implied object parameter to be of the type of the derived class.)

3
Pete Becker On

"Hiding" is shorthand for what's really going on. The rule is simple (<g>): when looking up a name, the compiler starts in the current scope; if it finds that name, it's done. If it doesn't find it, it moves to the next enclosing scope. Repeat until done.

Couple that with the rule that overloading occurs only among functions defined in the same scope. Once the compiler finds that name, all definitions in the same scope participate in overloading. The compiler doesn't look in outer scopes to find things that might match the name. That way lies madness.

Note that name lookup looks only for the name; it does not depend on whether the name that's found overrides a name in the base class, nor on whether there is more than one function with the same name in the current scope (i.e., the name is overloaded). Once the name is found the search ends. All definitions of that name in that scope participate in overloading.

So, void visit(B&) in visitor3 hides all of the other definitions of visit in all of the base classes. The name is defined in visitor3, so the compiler doesn't look anywhere else.