You'll also need to think about branch preditability. Are you changing the case in which the different function implementations are called randomly? It's best to test, because if the implementation keeps failing branch preditability, then a vtable would be more performative with the extra cycles and indirection compared to the wasted cycles from branch preditability failure.
Calling a direct function pointer is good, a direct call is better. Depending on what you're doing you could separate them based on the type before using for the direct call. Could do it runtime instead of compile time with the use of CRTP and etc.
You could directly store the function pointer and check the function pointer instead of what determines the function pointer to be called to avoid requiring branch preditability. You could also look to optimize that by abusing terinary expressions to hopefully get the compilier to optimize that as a cmov instruction rather than cmp, mov, etc separately.
So, what im saying is, it really depends on how dynamic the behavior is.
Boundary type is known at compile time -> Crtp/templates/sfinae/etc -> everything will get inlined normally or less dispatch cost.
Boundary type changes rarely -> can store function pointer / lambda -> Simplier than vtable, no base class indirection, no need to load vtable.
Boundary type changes frequeintly and less unpredictabily -> Vtable or pointer like above, but its more based on if the branch preditability fails or not.
(side note: if you know the most common outcome, you can also look in to using [[likely]] and [[unlikely]] compiler flags)
And if you just need the pure runtime flexibility, virtual functions and the vtable would just be better in the long time generally. You get cleaner code, and indirect branches are handled well nowadays.
The best thing is to create a benchmark of your implementation and the different ways to implement it.