C++: Can virtual inheritance be detected at compile time?
Asked Answered
M

8

22

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?

Myrica answered 23/5, 2010 at 22:41 Comment(9)
I think if Base is merely a superclass of a virtual base class of Derived, this should work with static_cast. At least i can't find the Standard say otherwise. You can use boost::is_virtual_base_of for checking the other case.Wynd
I can almost think of some ways, but they're ugly. If you can give me moar info about what you want to do with this, I might be able to come up with something.Marcin
In the case where Base is a superclass of a virtual base of Derived, gcc produces the error (for example): error: cannot convert from base ‘osg::Referenced’ to derived type ‘osgViewer::ViewerBase’ via virtual base ‘osg::Object’ Using the mental model that virtual base classes are implemented using pointers to the bases, the error makes sense to me: static_cast can convert the "super base" to the virtual base without a problem, but then it's in the same situation with respect to converting the pointer to the derived type.Myrica
@Myrica definitely not here: codepad.org/9cf7ZWK2Wynd
@litb My case is a bit different: codepad.org/MMlYcvJw. That does not compile, much as I would suspect.Myrica
@Tim, ah i see now! :) Those "superclass" and "subclass" terms always confuse me again :)Wynd
Look. It's impossible to make static_cast from virtual Base to Derived: codepad.org/rTZqb7hoSole
@Alexey Yes, that's the point. I want to be able to determine at compile time if dynamic_cast is needed. I came up with another method,codepad.org/zhG8yR1O, which still doesn't compile with gcc. That has me really wondering; it seems to me to be classic SFINAE stuffMyrica
@academicRobot gcc version 4.4.3 20100127 (Red Hat 4.4.3-4) (GCC)Myrica
H
13

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.

Hibbitts answered 6/6, 2010 at 8:57 Comment(7)
(-1) question doesn't mention the boost library. Answer shouldn't require a new third party library.Sapheaded
@Mike: where did you get that idea? One of C++'s major strengths is the vast array of high quality libraries available. Take a look at some other C++ questions and you'll find boost being suggested right and left. If the question says they can't use a third-party library, that's another story.Idolla
And even if you do not want the lib, you can at least look at boost to see how they implemented it.Leicestershire
@Mike. It's not a good idea to roll your own as boost deals with some edge cases for various compilers. However if you want to understand how to roll your own I've outlined it in my answer at #2894291Intellectual
There is no point of reducing 99% of C++ questions to "use boost" - boost turned into a monster conglomerate and not only that no one has the time to track it anymore, but also no one has the time to read through it. For simple things people don't want to incur dependency on yet another corpus of code and it's cross dependencies. That's the downfall of all big libraries and "frameworks" - they move like a herd.Abuttals
@ZXX: While your comment is amusing in its ambition, Boost is neither a big library nor a "framework". It is a collection of libraries. Most of them are incredibly lightweight: so much so, in fact, to not even require importing a static library into your own project, let alone shipping a dynamic one: they are header-only. It does not get any more lightweight than that.Engelbert
At the time I asked I would have been resistant to using Boost, but clearly using Boost's is_base_of and is_virtual_base_of, or at least looking at their implementation, is the right approach.Myrica
I
5

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.

Intellectual answered 29/7, 2010 at 16:3 Comment(4)
I have upvoted since other answers that solve the same subproblem are upvoted, but this does not solve the specific problem: detecting whether the inheritance is virtual --that is, not only that one is base of the other but that the relation is virtualPropagate
Edited the code to use is_virtual_base_of boost.org/doc/libs/1_43_0/libs/type_traits/doc/html/…Intellectual
Boost's solution is a very clever one and this answer now seems to be a complete answer to the OPs problem.Saponaceous
Thanks for the explanation of how Boost's is_virtual_base_of works.Myrica
A
3

First, your code is doing sizeof of a pointer instead of a dereferenced pointer so it wouldn't work even if gcc wasn't complaining.

Second, sizeof trick have to work with casts of 0 and not actual pointers or object - that guarantees zero overhead and also that it won't complie till you do it right.

3rd, you need to declare 2 templated classes or structs one deriving just from D, the other deriving from D and virtual B, and then cast 0 to their pointers, dereference them and then sizeof.

4th - Do you have any big reason for trying to be politically correct with static_cast instead of straight cast here? Compiler will always infer from that that you are looking for more whining and in this case you are definitelly not.

BTW you don't need to grab full code from Alexandrescu - just grab the core technique which basically just:

sizeof(*((T*)0))

Alexandrescu is really good at cleaning up after a trick.

Oh and remember that compiler is not supposed to evaluate sizeof args or instantiate unused templated classes and structs - so if it does then it's a compiler bug and if you force it to do that then it's your bug :-)

Once you have that, you need to define precisely and in positive terms wha your statement "if a pointer to Derived can be cast from a pointer to Base without dynamic_cast<>" actually means in terms of class relationships - just saying "without operator/function Q" doesn't makea problem well defined and you can't solve what you can't define - honest :-)

So just take the first clean step that compiles and then try to define in which way would the two cases you mentioned be different in reality - what would one have or do that the other one wouldn't.

Abuttals answered 5/8, 2010 at 12:53 Comment(3)
+1, points 1 and 2 are definitely on spot, I reserve my opinion on 3 and 4 for a later time when I get time to understand it and test it. Do you have any hints on how to precisely and in positive terms state that a static_cast from the derived to the base will work?Propagate
Nope - that's why I asked :-) I always suspect attempts to define a trait or a relation by a result of a construct like static_cast. You need to define what you are looking for first and then it may turn out that static_cast is irrelevant for determining patrticular trait at compile time or that a trait doesn't have observable effects so the trait is irrelevant, or it's observable only if something else is present as well.Abuttals
Side-note: remember how static_cast balked at virtual base? While there is a good reason for that with physical objects (downcast is impossible if not preceeded by equivalent upcast) it doesn't provide any trait information - it just yells "I can't determine" :-)Abuttals
C
2

Have you tried SUPERSUBCLASS from Loki?

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

Cuspid answered 28/5, 2010 at 18:3 Comment(1)
I've looked at the implementation of SUPERSUBCLASS in the Modern C++ Design book. I need more than the super sub class test; I want to know if the super can be downcast (upcast? :) to the sub using static_cast.Myrica
S
1

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.

Sapheaded answered 23/7, 2010 at 18:30 Comment(0)
C
1

There is a template hack to do it at compile time.

First you need to create an interface class like this:

template <typename T>
class SomeInterface
{
public:
    inline int getSomething() const;
};

template<typename T>
inline int SomeInterface<T>::getSomething() const
{
    return static_cast<T const*>(this)->T::getSomething();
}

The idea is: Cast this to T and call method with the same name and the same arguments from it. As you can see the wrapper function is inline so there will be no performance or call stack overheads during runtime.

And then create classes implementing the interface like this:

class SomeClass : public SomeInterface<SomeClass>
{
    friend class SomeInterface<SomeClass>;

public:
    int getSomething() const;
};

Then just add implementations of the derived methods normally.

This way might not look beautiful but exactly does the job.

Cackle answered 15/5, 2014 at 13:0 Comment(0)
W
0

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<>

Weaponeer answered 7/8, 2010 at 6:12 Comment(0)
B
-2

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.

Beeman answered 6/6, 2010 at 2:9 Comment(1)
There is a way of determining an object’s dynamic type at runtime, namely using dynamic_cast. Furthermore, a reinterpret_cast won’t do here, since the existence of multiple base classes leads to the necessity of adjusting the address when casting in the inheritance hierarchy.Clausen

© 2022 - 2024 — McMap. All rights reserved.