With c++17 it's quite easy to implement.
#include <type_traits>
// First, a type trait to check whether a type can be static_casted to another
template <typename From, typename To, typename = void>
struct can_static_cast: std::false_type{};
template <typename From, typename To>
struct can_static_cast<From, To, std::void_t<decltype(static_cast<To>(std::declval<From>()))>>: std::true_type{};
// Then, we apply the fact that a virtual base is first and foremost a base,
// that, however, cannot be static_casted to its derived class.
template <typename Base, typename Derived>
struct is_virtual_base_of: std::conjunction<
std::is_base_of<Base, Derived>,
std::negation<can_static_cast<Base*, Derived*>>
>{};
// Proof that it works.
struct Base{};
struct NonVirtual: Base{};
struct Virtual: virtual Base{};
static_assert(is_virtual_base_of<Base, NonVirtual>::value == false);
static_assert(is_virtual_base_of<Base, Virtual>::value == true);
See it on godbolt: https://godbolt.org/z/jxjq5W
With c++11 it's a tad less clean. Here it is:
#include <type_traits>
template <typename From, typename To, typename = void>
struct can_static_cast: std::false_type{};
template <typename From, typename To>
struct can_static_cast<From, To, decltype(static_cast<To>(std::declval<From>()), void())>: std::true_type{};
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, typename std::enable_if<
std::is_base_of<Base, Derived>::value &&
!can_static_cast<Base*, Derived*>::value
>::type>: std::true_type{};
struct Base{};
struct NonVirtual: Base{};
struct Virtual: virtual Base{};
static_assert(is_virtual_base_of<Base, NonVirtual>::value == false);
static_assert(is_virtual_base_of<Base, Virtual>::value == true);
See it on godbolt: https://godbolt.org/z/qnT6aq