The virtual table (in implementations that use such a thing) is a product of "compiler magic." It doesn't need to have a specific prototype because no C++ code ever uses it directly. Instead, the compiler custom-generates one to conform to each class that needs one. The compiler also generates the code to access each element, so it can guarantee that each one is accessed in a type-safe manner.
For example, the compiler knows that the slot for the A::setValue
method holds a pointer to a function that matches the setValue
signature because the compiler is the one that put it there in the first place. Furthermore, the only code that directly accesses that slot is machine code that the compiler generated, and prior to generating such code, the compiler already confirmed that the original C++ code was calling the setValue
function. Thus, there is no worry that the setValue
slot could ever hold anything other than a setValue
-conformant function pointer. Nor is there any concern that some other slot might be accessed instead; if that happened, it would be a compiler bug, never something that would happen as a result of ordinary user code.
The elements of the table are never treated as a group, so there's no requirement that they all have the same type. At best, they all have a type of "general pointer or offset suitable for the CPU to jump to." Since it's not really C++ at that point, the "type" doesn't have to fit any particular C++ type.