Of course, you can. Logically, if you are sure that an object X is type of A, then it means you can use it as A.
The simple and naive way to achieve this is using dynamic_cast, provided from standard C++. However it will use linear time to look into the vtable because dynamic_cast needs to check whether the given pointer actually can be casted to the given type, not like you who already know that X is type of A. Some platforms may not provide RTTI, in which case you can't use dynamic_cast.
There's another solution: let one of A or B know that it may be a superclass of that which is doing multiple inheritance.
#include <iostream>
#include <string>
struct Widget
{
virtual ~Widget() = default;
double widgetData;
};
struct DbItem
{
virtual ~DbItem() = default;
std::string nodeData;
};
struct GeneralItem
{
virtual ~GeneralItem() = default;
virtual void* cast(int type) = 0;
// virtual void const* cast(int type) const = 0; // Use this as well
// This is alternative for someone who don't like
// dynamic function!
void* ptrOfWidgetOrDbNode;
// If GeneralItem can know what it can be casted to.
// virtual Widget* toWidget() = 0;
// virtual DbItem* toDbItem() = 0;
};
enum { Widget_id, DbItem_id };
// Can be used as syntax candy.
Widget* toWidget(GeneralItem* gItem)
{
return static_cast<Widget*>(gItem->cast(Widget_id));
}
DbItem* toDbItem(GeneralItem* gItem)
{
return static_cast<DbItem*>(gItem->cast(DbItem_id));
}
struct TextView : Widget, GeneralItem
{
TextView() { widgetData = 20.0; }
void* cast(int type) override
{
switch ( type )
{
// WARNING: static_cast IS MANDATORY.
case Widget_id: return static_cast<Widget*>(this);
default: return nullptr;
}
}
};
struct ImageView : GeneralItem, Widget
{
ImageView() { widgetData = 40.0; }
void* cast(int type) override
{
switch ( type )
{
// WARNING: static_cast IS MANDATORY.
case Widget_id: return static_cast<Widget*>(this);
default: return nullptr;
}
}
};
struct SoundData : DbItem, GeneralItem
{
SoundData() { nodeData = "Sound"; }
void* cast(int type) override
{
switch ( type )
{
// WARNING: static_cast IS MANDATORY.
case DbItem_id: return static_cast<DbItem*>(this);
default: return nullptr;
}
}
};
struct VideoData : GeneralItem, DbItem
{
VideoData() { nodeData = "Video"; }
void* cast(int type) override
{
switch ( type )
{
// WARNING: static_cast IS MANDATORY.
case DbItem_id: return static_cast<DbItem*>(this);
default: return nullptr;
}
}
};
GeneralItem* getDbItem();
GeneralItem* getWidget();
int main()
{
{
// This is definitely subclass of Widget, but
// GeneralItem has no relationship with Widget!
GeneralItem* gItem = getWidget();
Widget* nowWidget = static_cast<Widget*>(gItem->cast(Widget_id));
std::cout << nowWidget->widgetData << std::endl;
delete gItem;
}
{
// This is definitely DbItem!
GeneralItem* gItem = getDbItem();
// DbItem* nowDbItem = static_cast<DbItem*>(gItem->cast(DbItem_id));
// You can use sugar!
DbItem* nowDbItem = toDbItem(gItem);
std::cout << nowDbItem->nodeData << std::endl;
delete gItem;
}
}
GeneralItem* getDbItem()
{
return new VideoData;
}
GeneralItem* getWidget()
{
return new TextView;
}
In this example code, there are 3 base classes, 2 getter functions, and some subclasses. Widget and DbItem are classes which you cannot touch, and GeneralItem is intended to be a superclass for classes which do multiple inheritance.
getDbItem() : GeneralItem*
is a function which returns GeneralItem which is definitely instance of DbItem as well. getWidget() : GeneralItem*
is similar one, which type of returned instance must be Widget.
GeneralItem has a virtual function cast(type_id) : void*
which returns pointer of given type, but in void pointer form because return type is determined at compile time. It can be cast back to the type you want by static_cast or reinterpret_cast.
You need to pay attention to arbitrary order of superclasses and static_cast in cast()
implementation: if you miss static_cast, your void pointer will be mapped to child class, not DbItem
or Widget
. Omitting static_cast will allow you to have clean, even without any warning compile result, since casting a typed pointer to void pointer is completely reasonable.
This is just one solution among various choices, so if you are looking for a solution which can be used in different situations, please let me know.