Hello,

I'm having some problems with inheritance.

The following code compiles and runs as expected: It prints out Hey: 1, since SomeFunction returns true.

#include <iostream>

class Base
{
public:
    virtual bool SomeFunction(const int& A) const = 0;
};

class Derived : public Base
{
public:
    void hey() const;
    //bool SomeFunction(const int& A, int& B) const { return false; }
};

class DerivedOfDerived : public Derived
{
public:
    bool SomeFunction(const int& A) const { return true; }
};

void Derived::hey() const
{
    std::cout << "Hey: " << SomeFunction(5) << std::endl;
}


int main(int argc, char **argv) 
{
    DerivedOfDerived Ouch;

    Ouch.hey();

    return 0;
}

But if I uncomment the line bool SomeFunction(const int& A, int& B) const { return false; } which should not affect anything, since I'm never even calling SomeFunction with 2 integers, it doesn't compile with the message:
main.cpp:24:40: error: no matching function for call to ‘Derived::SomeFunction(int) const’

What? Why did Derived stopped being a child of Base? Isn't that very weird behaviour? Is this a bug in GCC 4.7.2? I think it should be a bug. Or can someone explain to me why this is?

Thank you!
Xorlium

Recommended Answers

All 6 Replies

The problem is that by declaring a function in Derived that is the same name as the virtual function you declared in Base, you are actually hiding the original function that you declared in Base. These are little-known rules but they are rules nevertheless. When you have a function that takes two ints instead of one, as far as the compiler is concerned, the virtual function that only takes one argument no longer exists.

You can actually circumvent this behavior by adding using Base::SomeFunction; in the declaration of Derived.

This is not a bug, it is the expected behaviour. There is a rule in C++ that overloading of member functions is limited to a single class scope. Basically, calling SomeFunction on a Derived object causes the compiler to look for overloads within the Derived class only. If I compile your code with Clang (which has superior warning messages compared to GCC), I get the following messages:

overload_inheritance_test.cpp:13:10: warning: 'Derived::SomeFunction' hides overloaded virtual function [-Woverloaded-virtual]
    bool SomeFunction(const int& A, int& B) const { return false; }
         ^
overload_inheritance_test.cpp:6:18: note: hidden overloaded virtual function 'Base::SomeFunction' declared here
    virtual bool SomeFunction(const int& A) const = 0;
                 ^
overload_inheritance_test.cpp:24:43: error: too few arguments to function call, expected 2, have 1
    std::cout << "Hey: " << SomeFunction(5) << std::endl;
                            ~~~~~~~~~~~~  ^
overload_inheritance_test.cpp:13:5: note: 'SomeFunction' declared here
    bool SomeFunction(const int& A, int& B) const { return false; }
    ^

The error is the same as GCC (unresolved overload), as expected from any standard-compliant compiler, but what is of interest here is the warning preceding it. That basically sums it up, any function in a derived class with the same names as a function in the base-class will hide that base-class function from overload-resolution, which can be useful in some circumstances, but is mostly annoying. I don't really know what the reason is for this rule, but I imagine it has some technical ramifications, otherwise the standard committee wouldn't have imposed it.

The way to fix it is to use the using statement, as follows:

class Derived : public Base
{
public:
    void hey() const;

    bool SomeFunction(const int& A, int& B) const { return false; }

    // Bring the Base class function of the same name into 
    // the Derived class overload-resolution scope
    using Base::SomeFunction;
};

And access the Derived::SomeFunction function from the DerivedOfDerived class scope requires that you do that again there too:

class DerivedOfDerived : public Derived
{
public:
    bool SomeFunction(const int& A) const { return true; }
    using Derived::SomeFunction;
};

Which compiles without error or warning, and behaves as desired.

Thanks, that works!

Still, I think that's veeery strange behavior from C++ (IMHO), and the fact that it's what it is supposed to do is even stranger.

This discussion might enlighten you. You find this behavior strange, but understand that the inverse behavior could lead to results just as strange, if not stranger, considering that it would go against the more general scoping rules in C++. There are maintainability issues with allowing functions in the base-class to overload those of derived classes. That's a general maintainability rule, things in a broader more general scope should not change the behavior of code in a narrower sub-scope. This is why local variables take precedence over global variables of the same name, the same goes between namespace scopes, and between derived and base class scopes.

Think about this. Say you're part of a team of developers working on some big library / application. Your job is to work on class "Derived", and some other developer that you barely know works on class "DeepBase" which is somewhere a few inheritance levels up from the class you're working on. One day, that developer pushes a small change to the DeepBase class' code, the next day you wake up with the team screaming at you because the code in the Derived class broke the last nightly build. Now, you're stuck plowing through the code to find the source of the problem, and then, after a lot of pain-staking research and debugging, you realize that a call to a function of the Derived class was being re-routed (through overloading) to a newly added function in the DeepBase class. This is a maintenance nightmare. You just can't have that. That's the overall reasoning behind the general "narrowest scope first" rule for all lookups.

Well, no, I don't agree. The situation you describe would not happen and could never be a problem IMHO (although, what do I know? I'm sure the C++ designers are smarter than me), but still, the reasons I've read so far for hiding do not convince me at all and seem to make no sense.

If I'm a programmer working on Derived and someone changes Base, creating SomeFunction(int a) of Derived should have no effect on SomeFunction(string b) of Base and no one will think otherwise. If I call SomeFunction(someString) then Base::SomeFunction(string) will be used and if I call someFunction(5) then Derived::SomeFunction(int) will be used.

The way I understand overloading works, the functions SomeFunction(string) and SomeFunction(int) are completely different functions that happen to have the same name, and the compiler (and the programmer) always knows which one is getting called, so no confusion is possible. They just happen to have the same name for the sake of code clarity.

If Derived::SomeFunction(int) "hides" or "replaces" Base::SomeFunction(int), that would be completely expected behavior: that's what you want, after all.

Anyway, thanks everyone.

creating SomeFunction(int a) of Derived should have no effect on SomeFunction(string b) of Base and no one will think otherwise.

What if the function in the base class is Base::SomeFunction(const char* a) and there's a call to the function like this: my_derived_object.SomeFunction("foo");. There are all sorts of situations in which overloading can have surprising effects, things can get ambiguous or unintended overloads can be selected instead of the one you expect. You always have to be aware of that and be careful with overloading functions. If there is great code-distance between different overloads, it makes things worse and can cause bugs that are hard to find in a large code-base. The solution of hiding the base class functions with the option of explicitly pulling them up to the derived-class' scope is not a perfect solution, but it helps, and when weighting the pros and cons, seems like the better way to go. That's all.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.