This can be checked at compile time. The key is if we have a diamond pattern:

You can clearly distinguish D& from A& . However, if the inheritance is not virtual:

casting would be mixed. So try to make a diamond!
template <typename Base, typename Derived> class make_diamond { struct D2 : virtual Base { };
At this point, this is just another void_t like type trait:
template <typename Base, typename Derived, typename = void> struct is_virtual_base_of : std::false_type { }; template <typename Base, typename Derived> struct is_virtual_base_of<Base, Derived, void_t< decltype(static_cast<Base&>( std::declval<typename make_diamond<Base, Derived>::type&>())) >> : std::true_type { };
If the roll is unambiguous, the expression in partial specialization will be valid and this specialization will be preferable. If the listing is ambiguous, we will have a replacement failure and will eventually be the main one. Note that Base should not actually have any virtual member functions here:
struct A { }; struct B : public A { }; struct C : virtual A { }; std::cout << is_virtual_base_of<A, B>::value << std::endl;
And if it has some pure virtual member functions, we donβt need to redefine them, since we never build an object.
struct A2 { virtual void foo() = 0; }; struct B2 : public A2 { void foo() override { } }; struct C2 : virtual A2 { void foo() override { } }; std::cout << is_virtual_base_of<A2, B2>::value << std::endl;
Of course, if your class is marked final , this will not work. But then, if it were final , it would not matter what inheritance he had.