"Cannot convert from 'A' to 'B&'

113 Views Asked by At

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;
}
2

There are 2 best solutions below

0
On BEST ANSWER

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;
  }
}
0
On

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.