Pages

Tuesday, June 7, 2011

An exception to C++ inheritance

Consider this code, with a Base class and on Derived class. foo() in Derived class calls foo(int).

1 class Base {
2 public:
3         void foo(int a) {}
4 }
5
6 class Derived : public Base {
7 public:
8         void foo() {foo(0);}
9 }

Do you see any problem with the code above?

Neither did I!!!!! It is not that I am a master of C++. Just that I thought this was a basic inheritance stuff. The foo(int) in the derived class should resolve to the one in Base class. Surprisingly it doesn’t. This code when compiled gives an error of foo(int) not being defined. Though the exact error may differ from compiler to compiler.

Whereas, this code.

1 class Base {
2 public:
3         void foo(int a) {}
4 }
5
6 class Derived : public Base {
7 public:
8         void foo1() {foo(0);}
9 }

OR

this, compiles fine.

1 class Base {
2 public:
3         void foo(int a) {}
4 }
5
6 class Derived : public Base {
7 public:
8         void foo() {Base::foo(0);}
9 }

I was perplexed and exasperated to no end for not being able to figure out the reason. I felt that my basic C++ inheritance fundamentals are weak and that didn’t feel too good. And it came as a respite to know that this is an exception to the regular rule and was explained in the comments section here. I am pasting the comment here from AndreyT at stackoverflow.com for future reference. Though this explanation raised an interesting critique - was this done only to avoid the example given in the comment. As I couldn’t figure out any other scenario where there is an ambiguity for a type to be resolved.

AndreyT

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 counterintuitive, 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 typevoid *, 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.

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 migth be seen as a violaiton of IS-A relationsip 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.

Monday, June 6, 2011

Bootstrapping

This one took a really long time to come by, and maybe the idea in my head is faded. Or lets say I am not that enthusiastic to write about it anymore. I might be lazy as well.

Anyways, the general idea was this – ‘we use computers to make computers’.

This is a recurring theme all around us. Most import one is the life it self - the making of a conscious being from unconscious/inanimate things (as GEB book states it). And life is the ultimate goal of this bootstrapping process – that of a self-reflecting entity. Meaning we as humans can say I and address ourselves. On every step up in bootstrapping the entity goes to a higher level of abstraction and existence, challenging and defining new paradigms.

Go ahead and identify more such entities around you that follow this theme. You will be amazed at the variety of it.