Search code examples
c++c++11enable-if

std::is_base_of and virtual base class


Is there a way to identify whether or not a base class is a virtual base class?

std::is_base_of will identify a base class, but I'm looking for something like std::is_virtual_base_of to identify a virtual base class.

This is for SFINAE purposes where I want to use dynamic_cast (less performant) when std::is_virtual_base_of is true, and static_cast (more performant) when it is false.


Solution

  • namespace details {
      template<template<class...>class, class, class...>
      struct can_apply:std::false_type {};
      template<class...>struct voider{using type=void;};
      template<class...Ts>using void_t=typename voider<Ts...>::type;
      template<template<class...>class Z, class... Ts>
      struct can_apply<Z, void_t<Z<Ts...>>, Ts...>:std::true_type {};
    }
    template<template<class...>class Z, class... Ts>
    using can_apply = details::can_apply<Z, void, Ts...>;
    

    this lets us do modular SFINAE on a template application easily.

    template<class Dest, class Src>
    using static_cast_r = decltype(static_cast<Dest>( std::declval<Src>() ));
    template<class Dest, class Src>
    using can_static_cast = can_apply< static_cast_r, Dest, Src >;
    

    now we can determine if we can static cast.

    Now we implement it:

    namespace details {
      template<class Dest, class Src>
      Dest derived_cast_impl( std::true_type /* can static cast */ , Src&& src )
      {
        return static_cast<Dest>(std::forward<Src>(src));
      }
    
      template<class Dest, class Src>
      Dest derived_cast_impl( std::false_type /* can static cast */ , Src&& src )
      {
        return dynamic_cast<Dest>(std::forward<Src>(src));
      }
    }
    template<class Dest, class Src>
    Dest derived_cast( Src&& src ) {
      return details::derived_cast_impl<Dest>( can_static_cast<Dest, Src&&>{}, std::forward<Src>(src) );
    }
    

    Test code:

    struct Base { virtual ~Base() {} };
    
    struct A : virtual Base {};
    struct B : Base {};
    
    struct Base2 {};
    struct B2 : Base2 {};
    
    int main() {
      auto* pa = derived_cast<A*>( (Base*)0 ); // static cast won't work
      (void)pa;
      auto* pb = derived_cast<B*>( (Base*)0 ); // either would work
      (void)pb;
      auto* pb2 = derived_cast<B2*>( (Base2*)0 ); // dynamic cast won't work
      (void)pb2;
    }
    

    live example