This question is regarding a compiler error from Visual Studio 2019 in a C++ application operating within a Windows 10 OS. It is probably an intermediate-level or higher question.
Summary
I have this function:
template<typename T> T* Scene::GetComponent(EntityID entityID) {
CompTypeID typeID = TypeToID<T>();
if (HasComponent<T>(entityID)) {
CompIndex index = componentIndexes[typeID][entityID];
return components[typeID][index];
}
else return nullptr;
}
This function results in compiler error C2440:
'return': cannot convert from '_Ty' to 'T*' with [_Ty=Component*] and [T=Name]
This error also has these two messages attached to it:
message : Cast from base to derived requires dynamic_cast or static_cast
message : see reference to function template instantiation 'T *Scene::GetComponent(EntityID)' being compiled with [T=Name]
This error appears 3 times, each with T equaling Name, Transform, & Sprite (each of the components so far in my game engine)
Compiler Error C2440 on MSDN: https://docs.microsoft.com/en-us/cpp/error-messages/compiler-errors-1/compiler-error-c2440?view=vs-2019
Function Explanation
ECS Design
My Entity Component System stores entities and components via two arrays of arrays.
The first is componentIndexes, which is used to index by entity into the second array, components which will return based upon index, a component. Essentially, if you've heard of condensing arrays using a "sparse" array and a 'dense' array, componentIndexes is my sparse array and components is my dense array to store components in my scene.
In short:
componentIndexes stores an index to components of type CompIndex, provided: [ComponentType][entityID] | See Graphic 1A
components stores component data of type Component*, provided: [ComponentType][index] | See Graphic 1B
// An array of arrays of indexes into the component arrays, acquired via Entities and Component Types
// Y - std::array of sets with each element position being representative of a Component Type ID
// & each element value being X
// X - std::vector of indexes with each element position being representative of an Entity ID
// & each element value being representative of the position of the component in the component containter(components)
/****EXAMPLE:****
_E0_E1_E2_E3_ E = Entity ID | C = Component ID | i = Index
C0|i0|i1|~~|i2| **A ~~ IS REPRESENTATIVE OF A NULL/NAN VALUE (of actual value equal to -1)**
|--+--+--+--+- Entity 0 has only Component 0 at index 0
C1|~~|~~|i0|i1| Entity 1 has Components 0(indx1) and 2(indx0)
|--+--+--+--+- Entity 2 has only Component 1(indx0)
C2|~~|i0|~~|i1| Entity 3 has Components 0(indx2), 1(indx1), and 2(indx1)
****************/
std::array<std::vector<CompIndex>, TOTAL_COMPONENT_TYPES> componentIndexes;
Graphic 1A | componentIndexes
// An array of vectors of Component*s, holding each components' data
// Y - std::array of vectors with each element position being representative of a Component Type
// & each element value being X
// X - std::vector of Component*s with each element position being indexed into via Entity IDs
// & each element value holding the data concerning a certain component
/****EXAMPLE:****
_i0_i1_i2_i3_ CD = Component Data | C = Component ID | i = Index
C0|CD|CD|CD|~~| Component 0 has 3 components
|--+--+--+--+-
C1|CD|CD|~~|~~| Component 1 has 2 components
|--+--+--+--+-
C2|CD|CD|~~|~~| Component 2 has 2 components
****************/
std::array<std::vector<Component*>, TOTAL_COMPONENT_TYPES> components;
Graphic 1B | components
Line-by-Line
CompTypeID typeID = TypeToID<T>();
This function works as expected, and returns an enum CompTypeID that serves as a numerical representation of component types. This can be used to index into my componentIndexes and components arrays, based upon the component type I'm targeting.
if (HasComponent<T>(entityID)) {
. . .
}
else return nullptr;
This function works as expected, and returns a bool whether or not an entity has a specific component. It will return false if the provided type T is not a component or if the entity does not have that component. Else return nullptr is the self-explanatory fail-state of this function.
CompIndex index = componentIndexes[typeID][entityID];
return components[typeID][index];
These functions get and return the component, of type Component*. How this does this is explained in ECS Design. each element in components should be stored components - these are structs and are inherited from an empty struct Component (ex. struct Name : Component {};). Because they're inherited from the parent class Component, I should be able to store each child "component" in components because it stores types of Component*.
What I Think The Problem Is
The type T of GetComponent<T>(EntityID) is going to be equal to Name, Transform, or Sprite. The storage type of components is a Component*. While Name*s, Transform*s, & Sprite*s can all be stored in Component*, it appears that the compiler won't let me assign data of type Component* to data of type Name* (or other components) - so while theoretically I should be able to fetch the Name data from my array of Component*s, I have no way to do so due to that I can't send it away from the GetComponent function as a Name*.
I fear that this issue may be caused by a fundamental misunderstanding of how storing children in an array of parents works - maybe this only stores the parent part of the child?
I only can imagine this being fixed one of two ways.
- There may be a way for me to get the child's information, given the parent's pointer's location - maybe by offsetting the pointer in memory based upon the size of the parent and child pointer if they're contiguous with one another? I don't think C++ exposes memory references in that capacity, maybe that kind-of approach would only be possible using Assembly due to pointer information being hidden behind typenames (like
int*orComponent*). - The other way could be some magic using smart pointers or (as the message attached to the error said) explicit dynamic_cast-ing or static_cast-ing; but I have no clue how any of these 3 things work, what they do, how to use them, or when they should be used.
Those are my best guesses - they're really shots in the dark from me. If you have a better idea, please share it and I'll do my best to look into it. This is a compiler error that I found little documentation or talk about in my Google searches, so I figured it would be appropriate to contribute with this.
Disclaimer
Sometimes I find that I've missed some basic information that most people pick up early due to structured class curriculum. I'm guessing that this is where I will be learning about smart pointers and dynamic pointers.
Resources
Scene.h
#ifndef SCENE_H
#define SCENE_H
#pragma once
#include "Components.h"
#include "Systems.h"
#include "Entity.h"
#include <array>
#include <vector>
#include <set>
#include <queue>
class Scene {
private:
// An array of arrays of indexes into the component arrays, acquired via Entities and Component Types
// Y - std::array of sets with each element position being representative of a Component Type ID
// & each element value being X
// X - std::vector of indexes with each element position being representative of an Entity ID
// & each element value being representative of the position of the component in the component container(components)
/****EXAMPLE:****
_E0_E1_E2_E3_ E = Entity ID | C = Component ID | i = Index
C0|i0|i1|~~|i2| **A ~~ IS REPRESENTATIVE OF A NULL/NAN VALUE (of actual value equal to -1)**
|--+--+--+--+- Entity 0 has only Component 0 at index 0
C1|~~|~~|i0|i1| Entity 1 has Components 0(indx1) and 2(indx0)
|--+--+--+--+- Entity 2 has only Component 1(indx0)
C2|~~|i0|~~|i1| Entity 3 has Components 0(indx2), 1(indx1), and 2(indx1)
****************/
std::array<std::vector<CompIndex>, TOTAL_COMPONENT_TYPES> componentIndexes;
// An array of vectors of Component*s, holding each components' data, acquired via Index & Component Type
// Y - std::array of vectors with each element position being representative of a Component Type
// & each element value being X
// X - std::vector of Component*s with each element position being indexed into via Entity IDs
// & each element value holding the data concerning a certain component
/****EXAMPLE:****
_i0_i1_i2_i3_ CD = Component Data | C = Component ID | i = Index
C0|CD|CD|CD|~~| Component 0 has 3 components
|--+--+--+--+-
C1|CD|CD|~~|~~| Component 1 has 2 components
|--+--+--+--+-
C2|CD|CD|~~|~~| Component 2 has 2 components
****************/
std::array<std::vector<Component*>, TOTAL_COMPONENT_TYPES> components;
std::queue<EntityID> oldEntityIDs;
std::array<std::queue<CompIndex>, TOTAL_COMPONENT_TYPES> oldCompIndexes;
EntityID GenerateNewEntityID();
CompIndex GenerateNewCompIndex(CompTypeID compType);
public:
Scene();
void Update();
EntityID NewEntity();
EntityID GetEntityID(std::string entityName);
Signature GetEntityComponentSignature(EntityID entityID);
std::vector<EntityID> GetEntitiesWithComponents(Signature components);
void DeleteEntity(EntityID entityID);
// template<typename T> T* AddComponent(EntityID entityID); // Same issue as GetComponent
template<typename T> T* GetComponent(EntityID entityID);
template<typename T> bool HasComponent(EntityID entityID);
bool HasComponents(Signature signComponents, EntityID entityID);
template<typename T> void DeleteComponent(EntityID entityID);
void DeleteComponents(Signature signComponents, EntityID entityID);
};
#endif // SCENE_H
Scene.cpp
#include "Scene.h"
Scene::Scene() {
}
void Scene::Update() {
// RENDER SYSTEM
for (EntityID entityID : GetEntitiesWithComponents(RenderSystem::required)) {
RenderSystem::Update(GetComponent<Transform>(entityID), GetComponent<Sprite>(entityID));
}
}
EntityID Scene::GenerateNewEntityID() {
EntityID newID;
if (oldEntityIDs.empty()) {
newID = (EntityID)componentIndexes[0].size();
}
else {
newID = oldEntityIDs.front();
oldEntityIDs.pop();
}
return newID;
}
CompIndex Scene::GenerateNewCompIndex(CompTypeID typeID) {
CompIndex newIndex;
if (oldCompIndexes[typeID].empty()) {
newIndex = components[typeID].size();
}
else {
newIndex = oldCompIndexes[typeID].front();
oldCompIndexes[typeID].pop();
}
return newIndex;
}
EntityID Scene::NewEntity() {
// generate entity ID
EntityID entityID = GenerateNewEntityID();
// populate entity index
for (std::vector<CompIndex> componentSparse : componentIndexes) {
componentSparse[entityID] = NO_COMPONENT;
}
// add default entity components
//AddComponent<Name>(entityID);
//AddComponent<Transform>(entityID);
return entityID;
}
EntityID Scene::GetEntityID(std::string entityName) {
for (EntityID entityID : GetEntitiesWithComponents(IDName)) {
if (GetComponent<Name>(entityID)->name == entityName) {
return entityID;
}
}
return 0; // TODO: resolve "NO_ENTITY" case
}
Signature Scene::GetEntityComponentSignature(EntityID entityID) {
Signature signature;
// for each component type
for (CompTypeID i = (CompTypeID)0; i < TOTAL_COMPONENT_TYPES; i = (CompTypeID)(i + (CompTypeID)1)) {
// if component type is on entity
if (componentIndexes[i][entityID] != NO_COMPONENT) {
// mark that bit of the signature to true
signature.set(i);
}
}
return signature;
}
std::vector<EntityID> Scene::GetEntitiesWithComponents(Signature components) {
std::vector<EntityID> entities;
// TODO: write GetEntittiesWithComponents
return entities;
}
void Scene::DeleteEntity(EntityID entityID) {
// delete entity Components
DeleteComponents(GetEntityComponentSignature(entityID), entityID);
// delete entity indexes
for (std::vector<CompIndex> componentSparse : componentIndexes) {
componentSparse.erase(componentSparse.begin() + entityID);
}
// add ID to list of avaliable IDs
oldEntityIDs.push(entityID);
return;
}
// Same issue as GetComponent()
/*template<typename T> T* Scene::AddComponent(EntityID entityID) {
CompTypeID typeID = TypeToID<T>();
if (typeID == IDNotAComponent || componentsSparse[typeID][entityID] != NO_COMPONENT) {
return nullptr;
}
else {
CompIndex index = GenerateNewCompIndex(typeID);
componentsSparse[typeID][entityID] = index;
components[typeID][index] = new T; // TODO Ensure, does this work?? (T if not pointer could have issue?)
return &components[typeID][index];
}
}*/
template<typename T> T* Scene::GetComponent(EntityID entityID) {
CompTypeID typeID = TypeToID<T>();
if (HasComponent<T>(entityID)) {
CompIndex index = componentIndexes[typeID][entityID];
return components[typeID][index];
}
else return nullptr;
}
template<typename T> bool Scene::HasComponent(EntityID entityID) {
CompTypeID typeID = TypeToID<T>();
if (typeID == IDNotAComponent) {
return false;
}
else if (componentIndexes[typeID][entityID] == NO_COMPONENT) {
return false;
}
else return true;
}
bool Scene::HasComponents(Signature signComponents, EntityID entityID) {
// for each component type
for (CompTypeID i = (CompTypeID)0; i < TOTAL_COMPONENT_TYPES; i = (CompTypeID)(i + (CompTypeID)1)) {
// if component type is in component signature
if (signComponents.test(i)) {
// if component type is not on entity
if (componentIndexes[i][entityID] == NO_COMPONENT) {
return false;
}
}
}
return true;
}
template<typename T> void Scene::DeleteComponent(EntityID entityID) {
CompTypeID typeID = TypeToID(T);
if (typeID == nullptr || componentIndexes[typeID][entityID] != NO_COMPONENT) {
return;
}
else {
CompIndex index = componentIndexes[typeID][entityID];
// delete component
delete components[typeID][index];
components[typeID][index] = nullptr;
// delete component index
componentIndexes[typeID][entityID] = NO_COMPONENT;
oldCompIndexes[typeID].push(index);
return;
}
}
void Scene::DeleteComponents(Signature signComponents, EntityID entityID) {
// for each component type
for (CompTypeID i = (CompTypeID)0; i < TOTAL_COMPONENT_TYPES; i = (CompTypeID)(i + (CompTypeID)1)) {
// if component type is in component signature
if (signComponents.test(i)) {
// if component type is on entity
if (componentIndexes[i][entityID] != NO_COMPONENT) {
CompIndex index = componentIndexes[i][entityID];
// delete component
delete components[i][index];
components[i][index] = nullptr;
// delete component index
componentIndexes[i][entityID] = NO_COMPONENT;
oldCompIndexes[i].push(index);
}
}
}
return;
}
Entity.h
#ifndef ENTITY_H
#define ENTITY_H
#pragma once
#include <limits>
#include <cstdint>
typedef std::uint16_t EntityID;
// Maximum number of entities allowed in a scene
const EntityID MAX_ENTITIES = std::numeric_limits<EntityID>::max(); // 65535
#endif // ENTITY_H
Components.h
#ifndef COMPONENTS_H
#define COMPONENTS_H
#pragma once
#include <bitset>
#include "Name.h"
#include "Transform.h"
#include "Sprite.h"
enum CompTypeID {
IDName,
IDTransform,
IDSprite,
TOTAL_COMPONENT_TYPES,
IDNotAComponent
};
template<typename T> CompTypeID TypeToID() {
if (std::is_same<T, Name>()) {
return IDName;
}
else if (std::is_same<T, Transform>()) {
return IDTransform;
}
else if (std::is_same<T, Sprite>()) {
return IDSprite;
}
else return IDNotAComponent;
}
// Component Signature
typedef std::bitset<TOTAL_COMPONENT_TYPES> Signature;
// Component Index
typedef int CompIndex;
const CompIndex NO_COMPONENT = -1;
#endif // COMPONENTS_H
Name.h
#ifndef NAME_H
#define NAME_H
#pragma once
#include "Component.h"
#include <string>
struct Name : Component {
public:
std::string name;
};
#endif // NAME_H
Component.h
#ifndef COMPONENT_H
#define COMPONENT_H
#pragma once
struct Component {
};
#endif // COMPONENT_H