Say you have a std::vector class that holds type Item, and you store inherited types of items: Weapon, Potion, Armor, etc. How do you retrieve the item as the inherited class and not the base?
Asked
Active
Viewed 88 times
2
-
9You need a vector of pointers/references to avoid slicing. Then you need some way to detect the type of the object, either RTTI or a type flag. But the whole point of polymorphism is that you shouldn't need to know exact types. – cdhowie Jun 06 '17 at 14:36
-
1[Try visiting the items instead](https://stackoverflow.com/questions/10116057/visitor-pattern-explanation) – StoryTeller - Unslander Monica Jun 06 '17 at 14:40
2 Answers
6
How do you retrieve the item as the inherited class and not the base?
This suggests that you need closed-set polymorphism, which is provided by std::variant
or boost::variant
.
using Entity = std::variant<Weapon, Potion, Armor>;
// An `Entity` is either a `Weapon`, a `Potion`, or an `Armor`.
std::vector<Entity> entities;
struct EntityVisitor
{
void operator()(Weapon&);
void operator()(Potion&);
void operator()(Armor&);
};
for(auto& e : entities)
{
std::visit(EntityVisitor{}, e);
// Call the correct overload depending on what `e` is.
}

Vittorio Romeo
- 90,666
- 33
- 258
- 416
-
Interesting. While in the for loop, how do I know 'è' is of type Weapon now, for example? Or does visit() turn e into Weapon? – Phil Jun 06 '17 at 15:03
-
@Phil: `visit` handles that for you. If `e` is a weapon, then `EntityVisitor::operator()(Weapon&)` will be called. – Vittorio Romeo Jun 06 '17 at 15:06
-
-
Are `variant` and `visit` approved? I didn't know they made it into the standard. Notably http://en.cppreference.com doesn't either... – Jonathan Mee Jun 07 '17 at 02:08
-
1@JonathanMee - They are in the latest standard draft. http://eel.is/c++draft/variant – StoryTeller - Unslander Monica Jun 07 '17 at 06:42
-
@StoryTeller Well said. And I don't know why I couldn't find it on http://en.cppreference.com last night it's totally here: http://en.cppreference.com/w/cpp/utility/variant – Jonathan Mee Jun 07 '17 at 13:23
2
Your design has a bad smell in it. Storing item*
has the unfortunate cost of having to manage them outside the vector
. The only good motivation for a vector<item*>
would be to use the item
s polymorphically. By trying to detect the type you're demonstrating that you are not using them polymorphically.
A better solution would be to store these items in separate vectors, rather than a communal vector<item*> items
, so vector<weapon> weapons
, vector<potion> potions
, vector<armor> armors
.
So please don't use this code, but given vector<item*> items
you can do this:
for(auto i : items) {
if(dynamic_cast<weapon*>(i) != nullptr) {
auto iWeapon = static_cast<weapon*>(i);
// work with weapon here
} else if(dynamic_cast<potion*>(i) != nullptr) {
auto iPotion = static_cast<potion*>(i);
// work with potion here
} else {
assert(dynamic_cast<armor*>(i) != nullptr);
auto iArmor = static_cast<armor*>(i);
// work with armor here
}
}

Jonathan Mee
- 37,899
- 23
- 129
- 288
-
-
2@Phil Generally when you see all the `dynamic_cast`s it's a tip off that templatization should be used. – Jonathan Mee Jun 06 '17 at 16:02