I am working on a C++ project involving class inheritance and function parameter overloading. I have two base classes that should never be instantiated but only inherited (by design choice):
#include <iostream>
class Item {
public:
virtual void use() = 0;
};
class ItemSword : public Item {
public:
void use() override {}
};
class ItemPickaxe : public Item {
public:
void use() override {}
};
class Evaluator {
public:
void val(Item& item) {
std::cout << "get_val default, item type: " << typeid(item).name() << std::endl;
}
};
class EvaluatorZombie : public Evaluator {
public:
void val(ItemPickaxe& item) {
std::cout << "get_val zombie, item type: " << typeid(item).name() << std::endl;
}
};
int main() {
Evaluator evaluator = EvaluatorZombie();
auto item1 = ItemSword();
evaluator.val(item1); // "get_val default, item type: 9ItemSword"
auto item2 = ItemPickaxe();
evaluator.val(item2); // "get_val default, item type: 11ItemPickaxe",
//but I want "get_val zombie, item type: 11ItemPickaxe"
}
I essentially want the .val(ItemType)
call to first check if ItemType exists in the evaluator, and if it doesn't, then use the parent .val(Item). Where ItemType inherits from Item.
The specific item "type" may be different and is not known at compile time, so I realize this may be an issue.
All the solutions I have seen require either implementing all "items" in a single "get_val" method in the Evaluators, but I want to be able to implement multiple "get_vals" for different items in the Evaluator children classes, and then use a default when an Item is not implemented in a get_val.
So I want a solution that:
Any help or thoughts is appreciated, thank you.
UPDATE:
In your updated example, evaluator.val(item2)
does not call EvaluatorZombie::val()
like you want, because evaluator
is not declared as an EvaluatorZombie
to begin with. You are "slicing" the EvaluatorZombie
object into a base Evaluator
object, which is why Evaluator::val()
is being called.
You would need to change the declaration of evaluator
to be an EvaluatorZombie
object without slicing it, eg:
auto evaluator = EvaluatorZombie();
// or simply:
EvaluatorZombie evaluator;
You would also need to expose access to the Evaluator::val
method in EvaluatorZombie
, such as with a using
statement, otherwise EvaluatorZombie::val
will hide Evaluator::val
and you will get a compile error trying to pass anything other than an ItemPickaxe
to EvaluatorZombie::val()
:
class EvaluatorZombie : public Evaluator {
public:
using Evaluator::val; // <-- ADD THIS
void val(ItemPickaxe& item) {
std::cout << "get_val zombie, item type: " << typeid(item).name() << std::endl;
}
};
Or, declare evaluator
as an Evaluator*
(or std::unique_ptr<Evaluator>)
pointer. Polymorphic method dispatch only works via pointers and references.
That being said...
(original answer)
Make Evaluator::get_val()
be virtual
so EvaluatorZombie
can override it, and then use dynamic_cast
to test if the passed Item
is a specific derived type.
For example:
class Item {
public:
virtual ~Item() = default;
};
class Evaluator {
public:
virtual ~Evaluator() = default;
virtual int get_val(const Item& item) {
// use item as needed...
return ...;
}
};
class ItemSword : public Item {};
class ItemPickaxe : public Item {};
class EvaluatorZombie : public Evaluator {
public:
int get_val(const ItemSword& sword) {
// use sword as needed...
return ...;
}
int get_val(const Item& item) override {
const ItemSword *sword = dynamic_cast<const ItemSword*>(&item);
if (sword)
return get_val(*sword);
return Evaluator::get_val(item);
}
};
// calls EvaluatorZombie::get_val(const ItemSword&)
EvaluatorZombie{}.get_val(ItemSword{});
// calls EvaluatorZombie::get_val(const Item&)
// which calls Evaluator::get_val(const Item&)
EvaluatorZombie{}.get_val(ItemPickaxe{});
UPDATE:
Another option is to have each Evaluator
descendant register the types it is interested in, and then have get_val()
lookup the passed Item
in the registered data to know what it do next with it, eg:
class Item {
public:
virtual std::string Type() const { return "Item"; }
virtual ~Item() = default;
};
class Evaluator {
protected:
std::unordered_map<std::string, std::function<int(const Item&)>> get_val_funcs;
public:
virtual ~Evaluator() = default;
int get_val(const Item& item) {
auto iter = get_val_funcs.find(item.Type());
if (iter != get_val_funcs.end()) {
return iter->second(item);
}
return ...;
}
};
class ItemSword : public Item {
public:
std::string Type() const override { return "Sword"; }
};
class ItemPickaxe : public Item {
public:
std::string Type() const override { return "Pickaxe"; }
};
class EvaluatorZombie : public Evaluator {
public:
EvaluatorZombie() {
get_val_funcs["Sword"] = [this](const Item& item){
return get_val(static_cast<const ItemSword&>(item));
};
}
int get_val(const ItemSword& item) {
// use sword as needed...
return ...;
}
};
// calls EvaluatorZombie::get_val(const ItemSword&)
EvaluatorZombie{}.get_val(ItemSword{});
// calls EvaluatorZombie::get_val(const Item&)
// which calls Evaluator::get_val(const Item&)
EvaluatorZombie{}.get_val(ItemPickaxe{});
// calls Evaluator::get_val(const Item&)
// which calls EvaluatorZombie::get_val(const ItemSword&)
EvaluatorZombie evaluator;
Evaluator &eval_ref = evaluator;
eval_ref.get_val(ItemSword{});
Evaluator *eval_ptr = &evaluator;
eval_ptr->get_val(ItemSword{});