5

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

Goofy
  • 5,187
  • 5
  • 40
  • 56
  • what is interesting adding override for `foo(double)` in `Middle` fixes the issue. So you can workaround this issue by calling base class method in this extra override. – Marek R Nov 09 '17 at 19:45
  • 1
    A very similar question: https://stackoverflow.com/questions/6727087/c-virtual-function-being-hidden – Zdeněk Jelínek Nov 09 '17 at 19:46
  • The warning is correct; `foo(double)` in `Final` hides `foo(int)` in Middle. If you create an instance of `Final` and say `foo(1)` it will call the `double` version. – AndyG Nov 09 '17 at 20:14
  • and another one: https://stackoverflow.com/questions/18515183/c-overloaded-virtual-function-warning-by-clang – Andriy Tylychko Nov 09 '17 at 20:27
  • Martin Bonner's answer is correct - this is related to the hiding rule. I've also closed the question as a duplicate to a similar case in which the hiding rule prevents compilation. – Peter Nov 09 '17 at 20:29
  • @Peter : Damn! I tried to find a duplicate and failed :-( – Martin Bonner supports Monica Nov 09 '17 at 20:35

3 Answers3

5

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)

2

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

v1bri
  • 1,398
  • 8
  • 13
  • 3
    "It looks like Clang is giving priority to the function name...", "Even though the `Middle` class inherits the `void foo(double)` method from `Base`, Clang seems to be assuming...". You are describing completely banal and trivial *name hiding*, which is a well-known C++ feature not surprising to anyone here. It is not Clang that's "giving priority" or "seems to be assuming" anything. It is a basic feature of C++ language. – AnT stands with Russia Nov 09 '17 at 19:59
  • To strengthen what AnT said: any compiler that fails to call `Middle::foo(int)` when the argument is a double is utterly broken. – Martin Bonner supports Monica Nov 09 '17 at 20:05
0

The original intent of that -Woverloaded-virtual warning was to catch the following situations (in the era when override did not exist yet)

struct Base
{
    virtual void foo(double) {}
};

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

It generates the same warning. The compiler suspects that you mistyped the parameter list in the derived class's function declaration.

What you observe in your case is likely an unintended side-effect of that warning. In modern C++ override ensures that you parameter list is correct and intentional, but the compiler is still not convinced: a presence of a "hidden" overloaded version of this functions still makes it to issue a warning.

As you noted yourself, GCC handles this situation in a smarter way. Apparently in GCC -Woverloaded-virtual is not part of -Wall -Wextra - you have to specify it explicitly.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • 1
    https://wandbox.org/permlink/IILi2LkKeXPXlXif: try to uncommend `using` – Andriy Tylychko Nov 09 '17 at 19:46
  • The issue here is with overload resolution, not with overriding. The warning message is pretty clear about that. – Zdeněk Jelínek Nov 09 '17 at 19:47
  • @Zdeněk Jelínek: Nobody says that the issue is with overriding. My point is that `-Woverloaded-virtual` was intended to catch problems, which were eventually eliminated for good by `override`. `override` ensures that the issue caught by `-Woverloaded-virtual` is no loner possible. – AnT stands with Russia Nov 09 '17 at 19:49
  • @Gruffalo: So? What point are you trying to make? You example demonstrates simple name hiding (not related to `virtual` at all). The issue is as old as the world itself, but `-Woverloaded-virtual` has nothing to do with it. This is not what `-Woverloaded-virtual` was/is intended to catch. – AnT stands with Russia Nov 09 '17 at 19:50
  • ok, a little bit improved example: https://wandbox.org/permlink/JDn2yfZ2nqHo9NXu. clang is right about the warning as it's still possible to make a mistake. and the bug is not the most obvious one – Andriy Tylychko Nov 09 '17 at 19:51
  • @Gruffalo: Same problem. Your example has no relation to the intended purpose of `-Woverloaded-virtual`. – AnT stands with Russia Nov 09 '17 at 19:52
  • What is the intended purpose of `-Woverloaded-virtual`? Honest question as I cannot find a definition. btw -1 not mine, I'm still not sure what the right answer – Andriy Tylychko Nov 09 '17 at 20:12
  • I think you are mistaken. Even if the OP had used `override` the compiler would still issue the same warning - not because of mistyping, but because the name hiding is surprising. – Martin Bonner supports Monica Nov 09 '17 at 20:13
  • So, if you take the sample you posted in your answer and add a virtual in the derived class function signature, would that sample suddenly start to be right? Because to me it seems that it would still hide the virtual overload on the base class and that's my understanding of this warning. I suppose the compiler writers had the same. Is there actually a valid case in OO design where you would want to hide a virtual function in this way? It doesn't look very intentional. LSP seems broken here. – Zdeněk Jelínek Nov 09 '17 at 20:14
  • @Gruffalo: It is described here: https://docs.freebsd.org/info/gcc/gcc.info.Warning_Options.html. – AnT stands with Russia Nov 09 '17 at 20:16
  • @ZdeněkJelínek: `virtual` != `override`, AnT is taking specifically about `override` as it guarantees that an existing function was overriden – Andriy Tylychko Nov 09 '17 at 20:16
  • @Martin Bonner: Name hiding is surprising - no argument here. However, it is surprising regardless of whether the functions are virtual or not. The warning `-Woverloaded-virtual`, as the name suggests, is undoubtedly designed to target situations specific to virtual functions. It is not a general "name hiding is surprising" warning. – AnT stands with Russia Nov 09 '17 at 20:18
  • @AnT: thanks, you convinced me – Andriy Tylychko Nov 09 '17 at 20:22