253

Consider the code :

#include <stdio.h>

class Base {
public: 
    virtual void gogo(int a){
        printf(" Base :: gogo (int) \n");
    };

    virtual void gogo(int* a){
        printf(" Base :: gogo (int*) \n");
    };
};

class Derived : public Base{
public:
    virtual void gogo(int* a){
        printf(" Derived :: gogo (int*) \n");
    };
};

int main(){
    Derived obj;
    obj.gogo(7);
}

Got this error :

>g++ -pedantic -Os test.cpp -o test
test.cpp: In function `int main()':
test.cpp:31: error: no matching function for call to `Derived::gogo(int)'
test.cpp:21: note: candidates are: virtual void Derived::gogo(int*) 
test.cpp:33:2: warning: no newline at end of file
>Exit code: 1

Here, the Derived class's function is eclipsing all functions of same name (not signature) in the base class. Somehow, this behaviour of C++ does not look OK. Not polymorphic.

Rob Kennedy
  • 161,384
  • 21
  • 275
  • 467
Aman Aggarwal
  • 3,905
  • 4
  • 26
  • 38
  • 10
    brilliant question, i only discovered this recently too – Matt Joiner Oct 27 '09 at 07:30
  • 1
    Duplicate: http://stackoverflow.com/questions/411103/function-with-same-name-but-different-signature-in-derived-class – psychotik Oct 27 '09 at 04:30
  • 11
    I think Bjarne (from the link Mac posted) put it best in one sentence: "In C++, there is no overloading across scopes - derived class scopes are not an exception to this general rule." – sivabudh Feb 11 '10 at 17:56
  • 7
    @Ashish That link is broken. Here's the correct one (as of now) - http://www.stroustrup.com/bs_faq2.html#overloadderived – nsane Oct 25 '15 at 11:37
  • If you sure, it doesn't harm anything without hiding the base class gogo functions, you can avoid the hide by adding `using Base::gogo` in Derived class. – Nayana Adassuriya Feb 16 '16 at 04:16
  • 3
    Also, wanted to point out that `obj.Base::gogo(7);` still works by calling the hidden function. – forumulator Jan 30 '18 at 21:19

4 Answers4

440

Judging by the wording of your question (you used the word "hide"), you already know what is going on here. The phenomenon is called "name hiding". For some reason, every time someone asks a question about why name hiding happens, people who respond either say that this called "name hiding" and explain how it works (which you probably already know), or explain how to override it (which you never asked about), but nobody seems to care to address the actual "why" question.

The decision, the rationale behind the name hiding, i.e. why it actually was designed into C++, is to avoid certain counter-intuitive, unforeseen and potentially dangerous behavior that might take place if the inherited set of overloaded functions were allowed to mix with the current set of overloads in the given class. You probably know that in C++ overload resolution works by choosing the best function from the set of candidates. This is done by matching the types of arguments to the types of parameters. The matching rules could be complicated at times, and often lead to results that might be perceived as illogical by an unprepared user. Adding new functions to a set of previously existing ones might result in a rather drastic shift in overload resolution results.

For example, let's say the base class B has a member function foo that takes a parameter of type void *, and all calls to foo(NULL) are resolved to B::foo(void *). Let's say there's no name hiding and this B::foo(void *) is visible in many different classes descending from B. However, let's say in some [indirect, remote] descendant D of class B a function foo(int) is defined. Now, without name hiding D has both foo(void *) and foo(int) visible and participating in overload resolution. Which function will the calls to foo(NULL) resolve to, if made through an object of type D? They will resolve to D::foo(int), since int is a better match for integral zero (i.e. NULL) than any pointer type. So, throughout the hierarchy calls to foo(NULL) resolve to one function, while in D (and under) they suddenly resolve to another.

Another example is given in The Design and Evolution of C++, page 77:

class Base {
    int x;
public:
    virtual void copy(Base* p) { x = p-> x; }
};

class Derived : public Base{
    int xx;
public:
    virtual void copy(Derived* p) { xx = p->xx; Base::copy(p); }
};

void f(Base a, Derived b)
{
    a.copy(&b); // ok: copy Base part of b
    b.copy(&a); // error: copy(Base*) is hidden by copy(Derived*)
}

Without this rule, b's state would be partially updated, leading to slicing.

This behavior was deemed undesirable when the language was designed. As a better approach, it was decided to follow the "name hiding" specification, meaning that each class starts with a "clean sheet" with respect to each method name it declares. In order to override this behavior, an explicit action is required from the user: originally a redeclaration of inherited method(s) (currently deprecated), now an explicit use of using-declaration.

As you correctly observed in your original post (I'm referring to the "Not polymorphic" remark), this behavior might be seen as a violation of IS-A relationship between the classes. This is true, but apparently back then it was decided that in the end name hiding would prove to be a lesser evil.

Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • 4
    Great answer! Also, as a practical matter, compilation would probably get a lot slower if the name search had to go all the way to the top every time. – Drew Hall Oct 27 '09 at 11:24
  • I was searching for this answer since long time. – SunnyShah Oct 17 '10 at 17:02
  • 6
    (Old answer, I know.) Now will `nullptr` I would object to your example by saying "if you wanted to call the `void*` version, you should use a pointer type". Is there a different example where this can be bad? – GManNickG May 17 '11 at 19:26
  • 2
    @DrewHall: That seems like a data structure problem. The compiler should be smart enough to store the base and derived methods of a derived class in a hash table or similar structure for quick lookup instead of crawling the hierarchy. – Joseph Garvin Jun 06 '12 at 20:52
  • 3
    The name hiding isn't really evil. The "is-a" relationship is still there, and available through the base interface. So maybe `d->foo()` won't get you the "Is-a `Base`", but `static_cast(d)->foo()` *will*, including dynamic dispatch. – Kerrek SB Jan 09 '14 at 13:36
  • When I do `int n = pow(2,4)`, I have a compiler error saying that this call is ambiguous and can be matched to `T pow(T,T)` for `T=double` `T=float` or `T=long`. How come the undesired behavior justifying _name hiding_ (i.e. `foo(NULL)` can be matched to `foo(void *)` or `foo(int)`) is not handled via such an "ambiguous call" compiler error ? – BConic Mar 14 '14 at 18:47
  • @AldurDisciple: In the `pow` example, the error is generated because there's no exact match for your arguments. In `foo(NULL)` case with `NULL` defined as `0`, the `int` version provides exact match and no error is generated. – AnT stands with Russia Mar 14 '14 at 18:56
  • 1
    I'm still not clear why the overload would be considered undesirable when the number of parameters change. In facts, I was under the impression that the compiler was generating a different function name for each method prototype. – Robert Kubrick May 12 '14 at 21:14
  • 16
    This answer is unhelpful because the example given behaves the same with or without hiding: D::foo(int) will be called either because it is a better match or because it has hidden B:foo(int). – Richard Wolf Jul 08 '14 at 03:06
  • 2
    @RichardWolf: the other way it makes more sense. If we had B::foo(int) and D::foo(void*), then without name hiding call to D::foo(NULL) would still resolve to B::foo(int) because it's obviously better match but usually it's not what you desire. But frankly I can't think of any good name hiding rationalization other than the fact that "it's how name lookup works" - when you call a function in an inner scope, you expect to use a function from that inner scope, not from outer (possibly global) scope. – Michał Góral Feb 26 '15 at 19:36
  • 1
    Another dent in this answer is that everybody accepts exactly this seemingly strange behavior because it's is just what happens if you overload functions – except when you overload a base member function in a derived class. So this is a bad explanation of the rationals behind the rule. – sbi Jan 14 '16 at 07:59
  • @RichardWolf The example in the answer did not have B::foo(int), so your analysis of the value of the answer isn't correct. – mabraham Jan 27 '16 at 11:55
  • @sbi: then according to you what can be the good explanation of rationale behind this name hiding rule ? – Destructor Nov 26 '16 at 12:02
  • 2
    @Destructor: Oh, the explanation is just what AnT said. Stroustrup has said so on several occasions. (If you want answers to such questions, you should read his _Design and Evolution of C++_ book. A great read.) Only the example is not very helpful, and this is rule is questionable in general. (Note, please, how I said _"questionable"_, not _"wrong"_. I'm not sure and I am not prepared to put in the time required to find out. But I do find it worth questioning.) – sbi Nov 27 '16 at 21:24
  • 1
    Which is why nullptr was created so maybe a real life example that has not been solved? – Dan May 09 '19 at 19:03
  • 1
    Examples explaining the rationale usually - as they do here - use overloads that do not match the base class. However, the question is asking about a - very common - case where a derived case overrides a virtual method in the base class i.e. with the same signature. From what I can tell, this is the case even if the `override` keyword is used, and even if the base class method is pure virtual. It's not clear to me from this answer or others how this particular use-case could be confusing or error-prone: there is no additional overload. – Sam Brightman Jan 11 '22 at 17:10
  • @kuldeep The hard way! :) (This just bit me.) – Paul Sanders Jun 27 '22 at 10:25
  • @SamBrightman I'm with you there. I guess they wanted to keep the rule simple - C++ is hard enough to get your head around as it is. – Paul Sanders Jun 27 '22 at 10:27
54

The name resolution rules say that name lookup stops in the first scope in which a matching name is found. At that point, the overload resolution rules kick in to find the best match of available functions.

In this case, gogo(int*) is found (alone) in the Derived class scope, and as there's no standard conversion from int to int*, the lookup fails.

The solution is to bring the Base declarations in via a using declaration in the Derived class:

using Base::gogo;

...would allow the name lookup rules to find all candidates and thus the overload resolution would proceed as you expected.

Drew Hall
  • 28,429
  • 12
  • 61
  • 81
  • 11
    OP: "Why does an overridden function in the derived class hide other overloads of the base class?" This answer: "Because it does". – Richard Wolf Jul 08 '14 at 03:58
13

This is "By Design". In C++ overload resolution for this type of method works like the following.

  • Starting at the type of the reference and then going to the base type, find the first type which has a method named "gogo"
  • Considering only methods named "gogo" on that type find a matching overload

Since Derived does not have a matching function named "gogo", overload resolution fails.

JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
2

Name hiding makes sense because it prevents ambiguities in name resolution.

Consider this code:

class Base
{
public:
    void func (float x) { ... }
}

class Derived: public Base
{
public:
    void func (double x) { ... }
}

Derived dobj;

If Base::func(float) was not hidden by Derived::func(double) in Derived, we would call the base class function when calling dobj.func(0.f), even though a float can be promoted to a double.

Reference: http://bastian.rieck.ru/blog/posts/2016/name_hiding_cxx/

Sandeep Singh
  • 4,941
  • 8
  • 36
  • 56