Class with static compiler const member variable?

Started by
7 comments, last by Conny14156 9 years, 5 months ago

Hi,

I was wondering if it was possible to get a "compiler constant" inside a class?

What I meant with [Compiler constant] is the kind of constant that works with case statement or #defined or likes Enums and not just a Read-Only.

I have a base class that looks like


template<typename>
class IBaseComponent :public BaseComponent
{
public:
	const unsigned int getType()
	{
		return typeID;
	}
	static const unsigned int typeID;
	
};

I have a Derived class


class TransformComponent: public IBaseComponent<TransformComponent>
{
public:

	TransformComponent();
	//Relative to the Window
	Vector2<int> position;

	//Pixel count and not Multiply
	Vector2<int> size;

	//Yet TBD
	Vector2<int> rotation;
};

.cpp
const unsigned int IBaseComponent<TransformComponent>::typeID = 1000;//<-- The nr doesnt matter

The reason why I "need" a compiler const is that I need it for a switch statement


for(std::map<int,BaseComponent*>::const_iterator it = t->GetComponents()->begin(); it != t->GetComponents()->end(); it++)
{ 
             switch(it->second->getType())
             {
 
                       {
                               break;
                       }
             }
}

Like so, but case statement seems to require constant that is known when compiling/Linking. A alternative method to switch is that I can just use


for(std::map<int,BaseComponent*>::const_iterator it = t->GetComponents()->begin(); it != t->GetComponents()->end(); it++)
{	
	if(it->second->getType() == IBaseComponent<TransformComponent>::typeID)
	{	
		//Do stuffs
	}
}

But for my curiosity, is the a way to make it work?


enum test
{
    a
};
const unsigned int IBaseComponent<TransformComponent>::typeID = test::a; <--- tried to do this but to no avail 
Advertisement
So you're looking at a do it yourself job for RTTI?

You can make a variable the way you describe, and you can use your own integers and other numeric constants. A switch should work with known values that are legal in a switch (basically integers since it devolves to a jump table) and you can use direct comparisons also as you described above.

But really, it looks like you are looking for an alternate solution to something that is already provided to you.

The moment I noticed switch didnt work, I just switched to if else statments. But I just cannot rid the problem from my mind for some reason.

This is impossible before C++11. Plus you have the definition of `typeID` in a different translation; how can the compiler generate the right code for switch if it doesn't even know what you're comparing the value with? (C's `switch` is not a general replacement for `if` like it is in some other languages.)

With C++11 you can make your member variable/function `constexpr`. They'll need to be defined in the header of course. Note that VC++ does not yet support `constexpr` in any officially released version which is likely one reason you don't see it mentioned in games tutorials or books yet.

I'm not sure why you're trying to avoid a simple virtual function or even RTTI. Sure, many bigger games do for "reasons," but they also avoid things like a `std::map` of components (that's an inefficient way to store/lookup your components).

If you switch to a compiler that supports `constexpr` you might also want to consider looking into C++11/14 in general. Your code samples can be cleaned up considerably with a little application of modern C++.

Sean Middleditch – Game Systems Engineer – Join my team!

How would you recommend I would do it as virtual function? mind giving a example?

I need to know what type the component is, cause depending on the type I need to instantiate another component. Is it possible to somehow upcast with just the base class access?

PS

not really a problem seeing that IF does what I need, but I just feel like there would be a more proper way to do it. (But I guess dont fix what not broken argument works here)

A virtual function wouldn't really cut it, since you'd have to set a unique ID manually for every component type.

Last time I needed some IDs for my component types, I used something like this:


unsigned int componentTypeIdCounter = 0;

template <type COMPONENTTYPE>
unsigned int getTypeId()
{
  static unsigned int id = componentTypeIdCounter++;
  return id;
}

I haven't touched C++ in a few month, so that code might not compile...

Not very pretty and you might run into some problems when saving/loading comes into play and the components are initialized in a different order, but for me it worked.

The virtual function would replace the `if`, not the logic you're using in the `if`.

You should never have to care about the specific type of a component and test for it. If you are (like in this case), that implies there's something wrong with your component architecture.

In other words, replace this:

	if(it->second->getType() == IBaseComponent<TransformComponent>::typeID)
	{	
		//Do stuffs
	}
with this:

	it->second->DoStuffs();
If you're in a context where you need a specific component (e.g., syncing physics back with transform data) then don't loop through all the components. Just grab the one you need:

// this code is still far more verbose than it needs to be, but it's a mechanical translation of your current code
  auto const trnsIt = t->GetComponents().find(IBaseComponent<TransformComponent>::typeID);
  if (trnsIt != t->GetComponents().end())
    //Do stuffs
And if you can, cache that component, since looking it up every time (especially with a `std::map`) is probably more inefficient than keeping a pointer around (but as always, measure to be sure).

Even better though in these kinds of cases would be more purpose-built data structures so you can e.g. just memcpy the data you need from one place to another with zero looping, map lookups, or inner function calls.

Sean Middleditch – Game Systems Engineer – Join my team!

I agree with Sean that it's better to grab the components you need instead of looping over them. I've never really programmed an entity component system and I just whipped this up in half an hour, but you might find it useful.

Components:


class BaseComponent
{
protected:
	static uint32_t typeIdCounter_;
};

uint32_t BaseComponent::typeIdCounter_(0);

template<class T>
struct Component : public BaseComponent
{
	static uint32_t TypeId()
	{
		static uint32_t typeId = BaseComponent::typeIdCounter_++;
		return typeId;
	}
};

struct TransformComponent : public Component<TransformComponent>
{
	int x, y;

	TransformComponent(int x, int y) : x(x), y(y) {}
};

struct OtherComponent : public Component<OtherComponent>
{
	int foo;

	OtherComponent(int foo) : foo(foo) {}
};

Entity:


class Entity
{
public:
	template<class T>
	void AddComponent(T* component)
	{
		components[T::TypeId()] = component;
	}

	template<class T>
	T* GetComponent() const
	{
		auto it = components.find(T::TypeId());
		return (it != components.end()) ? static_cast<T*>(it->second) : nullptr;
	}

private:
	std::unordered_map<uint32_t, BaseComponent*> components;
};

Example:


int main()
{
	TransformComponent transform = { 2, 3 };
	OtherComponent other = { 4 };

	Entity entity;
	entity.AddComponent(&transform);
	entity.AddComponent(&other);

	auto t = entity.GetComponent<TransformComponent>();
	auto o = entity.GetComponent<OtherComponent>();

	std::cout << TransformComponent::TypeId() << "," << OtherComponent::TypeId() << '\n';	// 0,1
	std::cout << t->x << "," << t->y << '\n';						// 2,3
	std::cout << o->foo << "\n";								// 4

	return 0;
}

Got it to work

This topic is closed to new replies.

Advertisement