Difficulty: Medium · Puzzle 5 of 6 · Topic: String and Casting Puzzles
Predict every line this prints, and say why the two method calls through the base handle differ.
class B;
function string who(); return "B.who"; endfunction
virtual function string kind(); return "B.kind"; endfunction
endclass
class D extends B;
function string who(); return "D.who"; endfunction
virtual function string kind(); return "D.kind"; endfunction
function string only_d(); return "D.only"; endfunction
endclass
module p5;
B b;
D d, d2;
initial begin
d = D::new();
b = d;
$display("%s %s", b.who(), b.kind());
if ($cast(d2, b))
$display("%s %s %s", d2.who(), d2.kind(), d2.only_d());
b = B::new();
$display("cast_base=%0d", $cast(d2, b));
end
endmoduleA call resolves by the handle type unless the method is virtual, in which case it resolves by the object type. `$cast` checks the object type.
1) `b = d` is an implicit upcast; `b` still points to the D object.
2) `b.who()` is non-virtual, so it is bound at compile time to the handle type B and prints `B.who`.
3) `b.kind()` is virtual, so it dispatches on the object type D and prints `D.kind`.
4) `$cast(d2, b)` succeeds because the object is a D; through `d2` all three calls use D, giving `D.who D.kind D.only`.
5) `b = B::new()` makes the object a plain B, so the downcast fails and the function returns 0.Answer: B.who D.kind D.who D.kind D.only cast_base=0
Checked by running the code in Verilator 5; the printed output supports the answer above. Non-virtual calls follow the handle type, virtual calls the object type; Icarus 13 does not implement $cast.
Non-virtual methods are chosen by the handle type, virtual ones by the object type; `only_d()` is not callable through `b` at all, so a downcast with `$cast` is needed first.
Why can you assign `d2 = b` only with `$cast` but `b = d` without one? Model answer: an upcast is always legal because every D is a B; a downcast might point at a plain B (or another subclass), so the compiler requires `$cast`, which checks the object type at run time and returns 0 on failure.
I would split the calls into virtual and non-virtual, because a non-virtual call uses the handle type and a virtual call uses the object type. So through the base handle, who prints B and kind prints D, since the object is a D. After a successful downcast with cast, all three calls use D. Once the base handle points to a plain B object, the downcast fails and returns 0. The trap is expecting non-virtual calls to follow the object, and I would check the cast result before using the derived handle.