Home > Mobile >  How can I use dynamic_cast to get objects with a user-specified type?
How can I use dynamic_cast to get objects with a user-specified type?

Time:09-22

If there is a method, which aims to put objects with specific type into another list, how can I use a user-defined parameter in the dynamic_cast? I know, that I can not use the std::string parameter directly in dynamic_cast, but is there a better solution?

std::list<Car*> GetCarsOfType(std::string type)
    {
        std::list<Car*> carsOfType;

        for (int i = 0; i < cars.size();   i)
        {
            if (dynamic_cast<type> cars[i]) // This is not possible, but how can I solve this in a better way?
            {
                carsOfType.push_back(cars[i]);
            }
        }

        return carsOfType;
    }

CodePudding user response:

You can create a pure virtual function in the class Car that every class that inherits must implement:

#include <list>
#include <string>
#include <iostream>

class Car
{
    public:
    virtual std::string gettype() const = 0;
    virtual ~Car() = default;
};

class Ford: public Car
{
    std::string gettype() const { return "Ford";}
};

class VW: public Car
{
    std::string gettype() const { return "VW";}
};

std::list<Car*> cars {new Ford{}, new VW{}, new Ford{}};


std::list<Car*> GetCarsOfType(std::string type)
{
    std::list<Car*> carsOfType;

    for (const auto &car: cars)
    {
        if (car->gettype() == type)
        {
            carsOfType.push_back(car);
        }
    }

    return carsOfType;
}

int main()
{
    std::cout << GetCarsOfType("Ford").size() << std::endl;
    std::cout << GetCarsOfType("VW").size() << std::endl;
    std::cout << GetCarsOfType("BMW").size() << std::endl;
}

As a bonus here the compile time version, create a template function that filters for the template type:

#include <list>
#include <iostream>

class Car
{
    public:
    virtual ~Car(){}
};

class Ford: public Car {};
class VW: public Car {};
class BMW {};

std::list<Car*> cars {new Ford{}, new VW{}, new Ford{}};

template <class T>
std::list<Car*> GetCarsOfType()
{
    std::list<Car*> carsOfType;
    for (const auto &car: cars)
        if (dynamic_cast<T*>(car))
            carsOfType.push_back(car);
    return carsOfType;
}

int main()
{
    std::cout << GetCarsOfType<Ford>().size() << std::endl;
    std::cout << GetCarsOfType<VW>().size() << std::endl;
    std::cout << GetCarsOfType<BMW>().size() << std::endl;
}

CodePudding user response:

You could keep a central repository of the mapping between the named car type and the actual type if you wish. It could then be used to do the dynamic_cast test for you.

Example:

#include <algorithm>
#include <unordered_map>

std::list<Car*> GetCarsOfType(std::string type) {
    // map between the named type and the dynamic_cast test:
    static const std::unordered_map<std::string, Car*(*)(Car*)> cast_checkers{
        {"SUV", [](Car* c) -> Car* { return dynamic_cast<SUV*>(c); }},
        {"Limo", [](Car* c) -> Car* { return dynamic_cast<Limo*>(c); }}
    };

    std::list<Car*> carsOfType;

    // a filter that only returns true for those you want
    auto flt = [&type](Car* c) { return cast_checkers.at(type)(c) != nullptr; };

    std::ranges::copy_if(cars, std::back_inserter(carsOfType), flt);

    return carsOfType;
}

Or using a view:

#include <ranges>
#include <unordered_map>

std::list<Car*> GetCarsOfType(std::string type) {
    static std::unordered_map<std::string, Car* (*)(Car*)> cast_checkers{
        {"SUV", [](Car* c) -> Car* { return dynamic_cast<SUV*>(c); }},
        {"Limo", [](Car* c) -> Car* { return dynamic_cast<Limo*>(c); }}};

    // same filter as above:
    auto flt = [&type](Car* c) { return cast_checkers.at(type)(c) != nullptr; };

    // a view over those you want
    auto matchview = cars | std::views::filter(flt);

    // populate the container using the view:
    return {matchview.begin(), matchview.end()};
}
  • Related