Original Post
This FB thread reminded me of one of my favourite (?) annoyances with C++. I present exhibit A:
So. The question is, how is the necessity of line B justified? It seems inconsistent with the treatment of data members - without redeclaration (there's no real separation of declaration and definition of class data members), we can 'use' the data members in all ways, whereas with the function members, we can use them in all ways *except to define them*. Suddenly, we need to declare in order to add a definition, even though we couldn't possibly *remove* the function from the derived class. We can't define Bar in such a way that calling wibble() on a Bar object is *illegal*, as long as it inherits from Foo (and to inherit from Foo, you do need to see the whole class declaration of Foo in that translation unit, so that the Bar data layout can be deduced). But even though that call *must* be legal, and even though the compiler is apparently able to deduce that fact (as proven by the ability to create and invoke PMFs to the call), we are forced to *re-assert* that fact before we can change what the call *does*. What's going on? Am I missing some subtlety? Is there "something dangerous" you could do otherwise, or some complication to the compiler logic that would otherwise be required? I sure can't think of anything, and I like to think I know this stuff pretty well...
#include <iostream>
using namespace std;
struct Foo {
/*virtual*/ void wibble() { cerr << "foo::wibble" << endl; } // LINE A
};
struct Bar : Foo {
void wibble(); // LINE B
};
void Bar::wibble() { cerr << "bar::wibble" << endl; } // LINE C
int main() {
Foo f;
Bar b;
b.wibble();
void (Foo::*x)() = &Foo::wibble;
void (Bar::*y)() = &Bar::wibble; // LINE D
(f.*(x))();
//(f.*(y))(); // LINE E
(b.*(x))(); // LINE F
(b.*(y))();
}
Here I've created a function implemented in base and derived classes, as well as function pointers to the two versions. Obviously line E won't compile and is commented out (because a Foo isn't a Bar); it's just there so we see all the permutations of invoking a function pointer with an object. So, the beginner's complaint boils down to, effectively, "I didn't have line B and that caused line C to produce compile errors, although it seemed to work in this other case..." (where the other case is not reproducible, at least by me). And indeed, if we comment out line B, errors are reported on line D. So the question is, why is this? It seems inconsistent with the treatment of data members: we don't need to re-declare those (and in fact, doing so would duplicate the data within the derived object layout). But it gets worse when we consider what happens if we comment out both lines B and C: the program *still compiles*. In particular, line D still works. Think about that: Even though we can't define 'Bar::wibble' because "it doesn't exist", we can nonetheless make a pointer-to-member-function to it. And invoke it, too. Worse still, while the 'virtual' keyword is supposed to help us with doing things the "real OO way" (by enabling dynamic dispatch, and thus being key to "defining interfaces"), uncommenting it on line A doesn't even help here. The only visible effect it has is on line F (and then only when we have lines B and C both in, such that there is a different function available to to the dynamic dispatch). So. The question is, how is the necessity of line B justified? It seems inconsistent with the treatment of data members - without redeclaration (there's no real separation of declaration and definition of class data members), we can 'use' the data members in all ways, whereas with the function members, we can use them in all ways *except to define them*. Suddenly, we need to declare in order to add a definition, even though we couldn't possibly *remove* the function from the derived class. We can't define Bar in such a way that calling wibble() on a Bar object is *illegal*, as long as it inherits from Foo (and to inherit from Foo, you do need to see the whole class declaration of Foo in that translation unit, so that the Bar data layout can be deduced). But even though that call *must* be legal, and even though the compiler is apparently able to deduce that fact (as proven by the ability to create and invoke PMFs to the call), we are forced to *re-assert* that fact before we can change what the call *does*. What's going on? Am I missing some subtlety? Is there "something dangerous" you could do otherwise, or some complication to the compiler logic that would otherwise be required? I sure can't think of anything, and I like to think I know this stuff pretty well...