c++functionoverloading

C++ function parameter overload with parent-child arguments


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:

  1. Allows me to write multiple get_vals in the Evaluator children for each Item class
  2. Does not modify the original "Item" and "Evaluator", or at least does it to a minimum. I want the original "Item" and "Evaluator" to be agnostic to the children.

Any help or thoughts is appreciated, thank you.


Solution

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

    What is object slicing?

    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{});
    

    Online Demo


    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{});
    

    Online Demo