I thought I understood inheritance, and virtual functions, and function overloading, but I've got a case where something about the interplay between these features is eluding me.
Suppose I've got a simple base class containing an overloaded virtual function, and a second class derived from it:
class b {
public:
virtual int f() { return 1; }
virtual int f(int) { return 2; }
};
class d : public b {
public:
virtual int f(int) { return 3; }
};
Notice that the derived class d
overrides only one of the overloaded virtual functions.
I can instantiate an object of class d
and invoke f(int)
on it, no problem:
d x;
std::cout << x.f(0) << std::endl;
But when I try to call the 0-argument function:
std::cout << x.f() << std::endl;
it fails! gcc says "no matching function for call to 'd::f()'; candidates are: virtual int d::f(int)". clang says "too few arguments to function call, expected 1, have 0; did you mean 'b::f'?" Even though d
is derived from b
which has a 0-argument f()
method, the compiler is ignoring that, and trying to call d
's 1-argument method instead.
I can fix this by repeating the definition of the 0-argument function in the derived class:
class d : public b {
public:
virtual int f() { return 1; }
virtual int f(int) { return 3; }
};
Or, as suggested by clang's error message, I can use a goofy disambiguation syntax that I never would have guessed would work:
std::cout << x.b::f() << std::endl;
But my question is, what rule did I break, and what is that rule trying to enforce/protect/defend? What I thought I was trying to do here was exactly the sort of thing I thought inheritance was for.
This is known as name hiding.
When you declare a function with the same name in the derived class, all functions with the same name in the base are hidden.
In order to gain unqualified access to them, add a using
declaration into your derived class:
class d : public b {
public:
using b::f;
virtual int f(int) { return 3; }
};
Some explanation in addition to @TartanLlama's answer:
When the compiler has to resolve the call to f
, it does three main things, in order:
Name lookup. Before doing anything else, the compiler searches for a scope that has at least one entity named f
and makes a list of candidates. In this case, name lookup first looks in the scope of d
to see if there is at least one member named f
; if there weren't, base classes and enclosing namespaces
would be considered in turn, one at a time, until a scope having at least one
candidate was found. In this case, though, the very first scope the compiler
looks in already has an entity named f
, and then Name lookup stops.
Overload resolution. Next, the compiler performs overload resolution to pick the unique best match out of the list of candidates. In this case, the count of argument does not match, so it fails.
Accessibility checking. Finally, the compiler performs accessibility checking to determine whether the selected function can be called.
Reference for Name lookup
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With