partial inheritance of set of overloaded virtual functions
Asked Answered
G

2

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.

Glair answered 7/7, 2015 at 14:35 Comment(1)
this a very good discussion of the topic #1629268Blimp
W
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; }
};
Weeds answered 7/7, 2015 at 14:39 Comment(6)
Is it in the standards ?Histogen
@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.Weeds
@OthmanBenchekroun Also in 13.2 Declaration matching, with the same example (or nearly so) given by SteveSwaney
Thanks! And I now see that mine is a rather frequent question, see e.g. Stack Overflow here and here and in the C++ FAQ list here. 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?Glair
@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"?Weeds
@ TartanLlama: Good point about saying "no". And someone else pointed me to a good explanation by Lance Roberts at this question.Glair
S
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

Staccato answered 7/7, 2015 at 15:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.