Since B
is a child of A
, B
is A
,
Not quite. Since B
is a child of A
, any B
object is also an A
object. However, this is not symmetric, so it is overstating the case to claim that B
is A
. It is correct to claim "a B
is an A
", but dropping the indefinite articles ("a" and "an") makes the claim false.
why is a function pointer from "void to B
" isn't like a function pointer from "void to A
"?
Because B
is not A
. The return types are different, so the function types are different. A B
object can be converted to an A
object (by copying the A
sub-object), but that is an extra step. If you were to write
A value = B::boo();
then the compiler inserts a conversion from B
to A
after boo()
is called. The conversion might not require machine instructions (a.k.a. a "no-op"), but it is there nonetheless. (One side-effect of having a conversion is that copying cannot be elided.)
A value = static_cast<A&&>(B::boo());
If you were to invoke boo()
through a function pointer of the type A(*)()
, there would be nothing to tell the compiler to insert the conversion. Skipping the conversion could be an issue, particularly if a B
object had data members not in A
objects, especially if those data members had non-trivial destructors.
The language does not attempt to draw a line between the cases where skipping the conversion is and is not a problem. This applies to both the return type and the parameter types. The type of a function assigned to a pointer must match the pointer's type exactly, no conversions needed.
This is where function objects, like std::function
, are useful. A std::function<A()>
could store either A:foo
or B::boo
. This is because the function object will adapt and record what conversions are needed, similar to what happens when invoking a function directly. Unlike a function pointer, a function object does not require an exact match on types.