std::is_base_of and virtual base class
Asked Answered
M

4

6

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.

Mcqueen answered 29/8, 2017 at 21:50 Comment(2)
Found the same question here, where one of the responses summarizes how boost does it... #2894291Mcqueen
Not quite the same, but how about just SFINAEing on the validity of the static_cast expression?Quartziferous
B
2
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

Bobodioulasso answered 30/8, 2017 at 13:45 Comment(0)
P
1

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

Prurient answered 20/3, 2021 at 9:15 Comment(2)
Is there really a need for the is_virtual_base_of struct? couldn't you just have template<class Dest, class Src> inline constexpr bool can_static_cast_v = can_static_cast<Dest, Src>::value; then template <typename Base, typename Derived> inline constexpr bool is_virtual_base_of_v = std::is_base_of_v <Base, Derived> && (!can_static_cast_v<Base*, Derived*>);Input
@Input the struct can be used with metafunctions that take types rather than values. But if you don't need that, sure, you can just go ahead with the value alone.Prurient
C
0

Didn't see a C++20 solution, so have this extremely elegant concept:

/// Check if a type is virtually derived from all the provided BASE(s)  
template<class T, class...BASE>
concept VirtuallyDerivedFrom = ((::std::is_base_of_v<BASE, T>
        and not requires (BASE* from) { static_cast<T*>(from); }
    ) and ...);

Here's the proof

Crenate answered 26/3 at 8:45 Comment(1)
Just a comment, but in that case I'ld still keep a trait class (that way it can be easily used as a type parameter to act as a metafunction) and use that trait class for the conceptSoaring
S
0

The static_cast solutions posted here can't work for protected/private inheritance.

struct B {};
struct D : protected B {};
struct VD : virtual B {};

B * pb = nullptr;
static_cast<D*>(pb);  // error: 'B' is an inaccessible base of 'D'
static_cast<VB*>(pb); // error: cannot convert from pointer to base class 'B' to pointer to derived class 'VB' because the base is virtual

So a protected/private base class will be identified as a virtual base class in those implementations.

By checking out the Boost source for is_virtual_base_of, here is a working implementation:

template<typename B, typename D>
struct __vbase_helper : D, virtual B {};

template<typename B, typename D>
struct is_virtual_base_of : std::bool_constant<
    std::is_base_of_v<B, D> &&
    !std::is_same_v<B, D> &&
    sizeof(__vbase_helper<B, D>) == sizeof(D)>
{};
                                                                                                            
template<typename B, typename D>
constexpr bool is_virtual_base_of_v = is_virtual_base_of<B, D>::value;

But there will be warnings if you use it for protected/private base classes, which is harmless but not elegant at all.

Sushi answered 15/7 at 9:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.