You have a A * that is pointing to an instance of A, not A * that is actually pointing to an instance of B. That is why dynamic_cast returns a null pointer of type B *, it is not related to these being incomplete types or anything (in the linked code, both A and B are complete types), and thus this is defined behaviour for dynamic_cast. However after that a null pointer is accessed; a clever compile can know that dynamic_cast can fail at that point and the pb->func1(); can do anything including not causing a null pointer exception or anything at all, or even calling the pb1->func on something that is not B.
An example with an incomplete type would be:
#include <iostream>
using namespace std;
class A {
public:
virtual void func() {
cout << "func():: in class A" << endl;
}
void func1(){
cout<< "func1():: in class A";
}
};
class B;
int main() {
A a;
A* pa = &a;
B* pb = dynamic_cast<B*>(pa);
return 0;
}
Now if you compile this with G++, you get
y.cc: In function ‘int main()’:
y.cc:20:32: error: cannot dynamic_cast ‘pa’ (of type ‘class A*’) to
type ‘class B*’ (target is not pointer or reference to complete type)
B* pb = dynamic_cast<B*>(pa);
that is all sane C++ compilers will reject such code.