Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unclear warning on overridden virtual method [duplicate]

Tags:

c++

I have a Base class that provide some business logic and virtual methods, that can be further overridden. Additionally I want to extend some classes inheriting from Base with Decorator. Here's simplified setup:

struct Base
{
    ~Base() = default;
    virtual void foo(int) {};
    virtual void foo(double) {};
};

template<typename T>
struct Decorator : public T
{

};

struct Middle : public Decorator<Base>
{
    virtual void foo(int) override {};    
};

struct Final : public Middle
{
    virtual void foo(double) override {};    
};

When I compile code with clang and -Wall -Wextra I get following warning:

21 : <source>:21:18: warning: 'Final::foo' hides overloaded virtual function [-Woverloaded-virtual]
virtual void foo(double) override {};    
             ^

16 : <source>:16:18: note: hidden overloaded virtual function 'Middle::foo' declared here: type mismatch at 1st parameter ('int' vs 'double')
    virtual void foo(int) override {};    
                 ^

GCC doesn't complain and to be honest I don't know what clang finds wrong here.

I run recent recent clang and GCC using Compiler Explorer: https://godbolt.org/g/fC5XXT

like image 724
Goofy Avatar asked Nov 09 '17 19:11

Goofy


2 Answers

This warning is all about name hiding. If you declare a name (an override of foo) in a scope, it hide all declarations of that namme in "outer" scopes (in this case, base classes). Decorator is an irrelevance here.

struct Base
{
    ~Base() = default;
    virtual void foo(int) {};
    virtual void foo(double) {};
};

struct Middle : public Base
{
    void foo(int) override {};    
};

struct Final : public Middle
{
     void foo(double) override {};    
};

int main()
{
   Final f;
   f.foo(0.0); // Calls Final::foo(double);
   f.foo(0);   // *Also* calls Final::foo(double) - because Middle::foo(int) is hidden.
   Middle& m = f;
   m.foo(0);   // Calls Middle::foo(int);
   m.foo(0.0); // *Also* calls Middle::foo(int) - because Base::foo(double) is hidden.
   Base& b = m;
   b.foo(0);   // Calls Middle::foo(int) - because that overrides Base::foo(int) and
               // the dynamic type of b is a (sub-class of) Middle.
   b.foo(0.0); // Calls Final::foo(double) - because that override Base::foo(double) and
               // the dynamic type of b is Final.
   return 0;

}

The behaviour of the calls to m and f are surprising to many, so Clang issues a warning. You can suppress it with:

struct Middle : public Base
{
    using Base::foo;
    void foo(int) override {};    
};

struct Final : public Middle
{
     using Middle::foo;
     void foo(double) override {};    
};

In which case all classes will have foo(int) and foo(double)

like image 117
Martin Bonner supports Monica Avatar answered Nov 04 '22 02:11

Martin Bonner supports Monica


Edit to incorporate AnT's comment's:

Clang is behaving correctly by following C++'s name hiding rule. A short and sweet description is available here. In brief...

A member of a derived class hides any member of a base class that has the same name as the derived class member.

This includes base class methods marked virtual.

Original answer follows:

It looks like Clang is giving priority to the function name and not the signature when deciding which method you are trying to call. Here is an example usage of your classes...

int main(void) {
    Final f;
    f.foo(3.14159);
    f.foo(0);

    Middle* m = static_cast<Middle*>(&f);
    m->foo(3.14159);
    m->foo(0);

    Base* b = static_cast<Base*>(&f);
    b->foo(3.14159);
    b->foo(0);
}

Notice the additional warning generated at the call site...

32 : <source>:32:12: warning: implicit conversion from 'double' to 'int' changes value from 3.14159 to 3 [-Wliteral-conversion]
    m->foo(3.14159);
       ~~~ ^~~~~~~

https://godbolt.org/g/qkjqEN

Even though the Middle class inherits the void foo(double) method from Base, Clang seems to be assuming you meant to call the void foo(int) override method declared in Middle.

As others have mentioned, you can add more overrides to help Clang resolve the method you intended to call. Another solution is provided by this StackOverflow question with the using keyword. The declarations in Middle and Final would become the following...

struct Middle : public Decorator<Base>
{
    using Base::foo;
    void foo(int) override { std::cout << "Middle::foo" << std::endl; };
};

struct Final : public Middle
{
    using Base::foo;
    void foo(double) override { std::cout << "Final::foo" << std::endl; };
};

https://godbolt.org/g/Qe1WMm

like image 2
v1bri Avatar answered Nov 04 '22 03:11

v1bri