C++: Can virtual inheritance be detected at compil

2019-03-11 03:23发布

I would like to determine at compile time if a pointer to Derived can be cast from a pointer to Base without dynamic_cast<>. Is this possible using templates and metaprogramming? This isn't exactly the same problem as determining if Base is a virtual base class of Derived, because Base could be the super class of a virtual base class of Derived.

Thanks, Tim Update: I felt good about this method:

#include <iostream>

using namespace std;

class Foo
{
};

class Bar : public Foo
{
};

class Baz : public virtual Foo
{
};

class Autre : public virtual Bar
{
};

typedef char Small;
class Big { char dummy[2]; };

template<typename B, typename D>
struct is_static_castable
{
    const B* foo;
    char bar[1];
    static Small test(char(*)[sizeof(static_cast<const D*>(foo)) == sizeof(const D*)]);
    static Big test(...);
    enum { value = (sizeof(test(&bar)) == sizeof(Small)) };
};

int main()
{

    cout << "Foo -> Bar: " << is_static_castable<Foo, Bar>::value << "\n";
    cout << "Foo -> Baz: " << is_static_castable<Foo, Baz>::value << "\n";
    cout << "Foo -> Autre: " << is_static_castable<Foo, Autre>::value << "\n";
}

But it doesn't work with gcc:

multi-fun.cpp: In instantiation of ‘is_static_castable<Foo, Baz>’:
multi-fun.cpp:38:   instantiated from here
multi-fun.cpp:29: error: cannot convert from base ‘Foo’ to derived type ‘Baz’ via virtual base ‘Foo’
multi-fun.cpp:29: error: array bound is not an integer constant
multi-fun.cpp: In instantiation of ‘is_static_castable<Foo, Autre>’:
multi-fun.cpp:39:   instantiated from here
multi-fun.cpp:29: error: cannot convert from base ‘Foo’ to derived type ‘Autre’ via virtual base ‘Bar’
multi-fun.cpp:29: error: array bound is not an integer constant

Am I confused about what can be done with the sizeof() trick?

标签: c++ templates
8条回答
我想做一个坏孩纸
2楼-- · 2019-03-11 03:36

Once converted to the base pointer, you can only get a runtime error (dynamic_cast). You can define the methods using templated params and get a compile error by using template specializations.

查看更多
Anthone
3楼-- · 2019-03-11 03:39

Have you tried SUPERSUBCLASS from Loki?

http://loki-lib.sourceforge.net/

查看更多
叛逆
4楼-- · 2019-03-11 03:48

Here is a solution for redirecting the compiler to do something depending on whether the class is a subclass of another or not.

class A 
{};

class B : virtual public A
{};

class C : public A
{};

// Default template which will resolve for 
// all classes
template 
< typename T
, typename Enable = void 
>
struct FooTraits
{
    static void foo(){
        std::cout << "normal" << std::endl;
    }
};

// Specialized template which will resolve
// for all sub classes of A
template 
< typename T 
>
struct FooTraits 
    < T
    , typename boost::enable_if
         < boost::is_virtual_base_of< A, T>
         >::type
    >
{
    static void foo(){
        std::cout << "virtual base of A" << std::endl;
    }
};

int main(int argc, const char * argv[] ){
    FooTraits<C>::foo(); // prints "normal"
    FooTraits<B>::foo(); // prints "virtual base of A"
}

and if you want to know how boost did it. If you have class Base and class Derived then the following holds.

struct X : Derived, virtual Base 
{
   X();
   X(const X&);
   X& operator=(const X&);
   ~X()throw();
};

struct Y : Derived 
{
   Y();
   Y(const Y&);
   Y& operator=(const Y&);
   ~Y()throw();
};

bool is_virtual_base_of = (sizeof(X)==sizeof(Y)));

It's a trick of using virtual inheritence with multiple inheritience. Multiple inheritience from the same virtual base does not result in duplicates of the virtual base class and therefore you can test this with sizeof.

查看更多
虎瘦雄心在
5楼-- · 2019-03-11 03:49

This may be a little naive (I'm much stronger in C than I am in C++) so I might not understand what you're trying to do, but if it's casting pointers you're talking about, C-style casts work perfectly well (eg. (D *)foo), or the C++ equivalent reinterpret_cast. That being said, this can be very dangerous, because you don't have any runtime checking, and thus need to be sure that you're casting into the correct type. Then again, if you wanted to have an easy way to check whether this is a correct assumption or not, we are back to square one. However, it appears you are trying to compare pointers above, which are all the same (they are basically integers). As far as I know, there is no way to determine an object's class at runtime in C++, including sizeof, which works at compile time. Basically, there's no way to do what you want to do (at least not with standard C++), however the actual cast wont cause any issues, just using the newly cast pointer in improper ways. If you absolutely need this functionality, you would probably be best to include a virtual function in your base class that reports what class it is (preferably with an enum value), and overload it in whichever subclass you hope to determine whether you can cast too.

查看更多
一夜七次
6楼-- · 2019-03-11 03:50

I had the same problem, once. Unfortunately, I'm not quite sure about the virtual-problem. But: Boost has a class named is_base_of (see here) which would enable you to do smth. like the following

BOOST_STATIC_ASSERT((boost::is_base_of<Foo, Bar>::value));

Furthermore, there's a class is_virtual_base_of in Boost's type_traits, maybe that's what you're looking for.

查看更多
贼婆χ
7楼-- · 2019-03-11 03:51

If you want to know at compile time you can take the derived class as an parameter but, if the only thing you have is the Base then you cannot know whether it refers to any of the foo,bar,etc.. class. This check can only be done where the pointer is converted to a Base. I think that's the whole purpose of dynamic_cast<>

查看更多
登录 后发表回答