This is possible to check at compile time. The key is that, if we have a diamond pattern:
You can unambiguously cast D&
to A&
. However, if the inheritance is non-virtual:
the cast would ambiguous. So let's try to make a diamond!
template <typename Base, typename Derived>
class make_diamond {
struct D2 : virtual Base { }; // this one MUST be virtual
// otherwise we'd NEVER have a diamond
public:
struct type : Derived, D2 { };
};
At which point it's just another void_t
-style 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 cast is unambiguous, the expression in the partial specialization will be valid, and that specialization will be preferred. If the cast is ambiguous, we'll have a substitution failure, and end up with the primary. Note that Base
here doesn't actually need to have any virtual
member functions:
struct A { };
struct B : public A { };
struct C : virtual A { };
std::cout << is_virtual_base_of<A, B>::value << std::endl; // 0
std::cout << is_virtual_base_of<A, C>::value << std::endl; // 1
And if it has any pure virtual member functions, we don't have to override them since we're never actually constructing 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; // 0
std::cout << is_virtual_base_of<A2, C2>::value << std::endl; // 1
Of course if your class is marked final
, this won't work at all. But then, if it were final
, it wouldn't matter what kind of inheritance it had anyway.