-1

I'm building an Entity-Component system using template metaprogramming. I keep getting either Cannot convert from [base type] to [type user requested]& or Cannot convert NullComponent to [type user requested]& errors:

class Entity {
public:
    Entity() = default;
    ~Entity() = default;

    template<typename C, typename... Args>
    void AddComponent(Args&&... args);

    template<typename C>
    C& GetComponent();

protected:
private:
    //...add/get helper methods here...

    unsigned int _id;
    std::vector<std::unique_ptr<IComponent>> _components;
};

template<typename C>
C& Entity::GetComponent() {
    for(auto c : _components) {
        if(std::is_base_of<a2de::IComponent&, C&>().value && std::is_same<decltype(c), C&>().value) {
            return *c; //<-- error here
        }
    }
    return NullComponent(); //<-- and here
}

EDIT

These options seem to work for now.

template<typename C>
const C& Entity::GetComponent() const {
    for(auto& uc : _components) {
        auto* c = dynamic_cast<C*>(uc.get());
        if(c && std::is_base_of<a2de::IComponent&, C&>().value && std::is_same<decltype(c), C&>().value) {
            return *c;
        }
    }
    throw std::runtime_error(std::string("Component not available."));
}

OR

class Entity {
public:
    //same as before...
protected:
private:
    //same as before...
    a2de::NullComponent _null_component;
};

template<typename C>
const C& Entity::GetComponent() const {
    for(auto& uc : _components) {
        auto* c = dynamic_cast<C*>(uc.get());
        if(c && std::is_base_of<a2de::IComponent&, C&>().value && std::is_same<decltype(c), C&>().value) {
            return *c;
        }
    }
    return _null_component;
}
Casey
  • 10,297
  • 11
  • 59
  • 88
  • Analysis would be easier if you posted a complete sample that you want to compile (the current code is missing includes, IComponent and NullComponent). – Rumburak Jan 25 '16 at 07:59
  • and did you forget to deference `c` in decltype? -> `std::is_same().value` -> `std::is_same().value` – Angelus Mortis Jan 25 '16 at 08:10
  • In the "seem to work for now" solutions, why do you need the `std::is_base_of` and the `std::is_same`? Isn't dynamic_cast taking care of everything you need? Also, I am pretty sure that the references in the `is_base_of` stop the code from working correctly. – Rumburak Jan 25 '16 at 21:11

2 Answers2

2

At least three things:

  • In GetComponent() you iterate over unique_ptr elements and compare their type (always std::unique_ptr<IComponent>) with something else in the std::is_same. You probably don't want that.
  • You are returning a reference to a temporary in the final return, it seems.
  • return *c needs a dynamic_cast unless C == IComponent.

EDIT

Also:

  • std::is_base_of makes no sense with references. Even with class NullComponent : IComponent {};, you would still get std::is_base_of<IComponent&, NullComponent&>::value == false.
  • And you do not check for nullptr

In the end, it seems to me that you should replace your for loop with

for(auto& component : _components) {
  auto* c = dynamic_cast<C*>(component.get());
  if (c)
  {
    return *c;
  }
}
Rumburak
  • 3,416
  • 16
  • 27
0

At a high level, from what I can figure out, the return type cannot be used to define the template type. The parameter list can be used to define the template type.

So, for example, this might work -

template<typename C>
void Entity::GetComponent(C *obj) {
    for(auto c : _components) {
        if(std::is_base_of<a2de::IComponent&, C&>().value && std::is_same<decltype(c), C&>().value) {
            obj = c; //<-- error here
            return;
        }
    }
    obj = NULL;
    return; //<-- and here
}

Hope this helps.

buchipper
  • 606
  • 4
  • 16