10

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.

Steve Summit
  • 45,437
  • 7
  • 70
  • 103
  • this a very good discussion of the topic http://stackoverflow.com/questions/1628768/why-does-an-overridden-function-in-the-derived-class-hide-other-overloads-of-the –  Jul 07 '15 at 15:08

2 Answers2

7

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; }
};
TartanLlama
  • 63,752
  • 13
  • 157
  • 193
  • 1
    Is it in the standards ? – Othman Benchekroun Jul 07 '15 at 14:41
  • 3
    @OthmanBenchekroun [basic.scope.hiding]/1 `A name can be hidden by an explicit declaration of that same name in a nested declarative region or derived class.` – TartanLlama Jul 07 '15 at 14:42
  • @OthmanBenchekroun Also in 13.2 Declaration matching, with the same example (or nearly so) given by Steve – marom Jul 07 '15 at 14:45
  • Thanks! And I now see that mine is a rather frequent question, see e.g. Stack Overflow [here](http://stackoverflow.com/questions/14212190/c-issue-with-function-overloading-in-an-inherited-class) and [here](http://stackoverflow.com/questions/1896830/why-should-i-use-the-using-keyword-to-access-my-base-class-method) and in the C++ FAQ list [here](https://isocpp.org/wiki/faq/strange-inheritance#hiding-rule). But I'm still left wondering... why? Is it just to make it easier on the compiler, or is this kind of inheritance thought to be a bad idea? – Steve Summit Jul 07 '15 at 14:54
  • @SteveSummit you can probably find a more official source on it and I haven't thought about it in any great detail, but I guess it's there to stop you shooting yourself in the foot by inheriting functionality that you are actively trying to block in your derived class. Also, if that rule wasn't there, how would you say "I don't want this base class function to be visible"? – TartanLlama Jul 07 '15 at 15:17
  • @ TartanLlama: Good point about saying "no". And someone else pointed me to a good explanation by Lance Roberts at [this question](http://stackoverflow.com/questions/1628768/why-does-an-overridden-function-in-the-derived-class-hide-other-overloads-of-the). – Steve Summit Jul 07 '15 at 15:32
2

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:

  1. 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.

  2. 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.

  3. Accessibility checking. Finally, the compiler performs accessibility checking to determine whether the selected function can be called.

Reference for Name lookup

songyuanyao
  • 169,198
  • 16
  • 310
  • 405