Search code examples
c++templatesentity-component-system

C++ How to cache a variable of template type T in a class?


Suppose that I have a Foo class like this, and I need many instances of it.

class Foo {
  public:
    Pool* bars;  // a global list of bars, each may have a different type

    template<typename T>
    T& AddBar(int x) {
        return bars->emplace<T>(x);
    }

    template<typename T>
    T& GetBar() {
        return bars->get<T>();  // a very slow function
    }
}

All the instances of Foo share the same pool of bars, which contains many bars of possibly different types. For example, bars may be a list of bars {A bar1, B bar2, A bar3, C bar4} where ABC are some class types, but each Foo foo instance can only have one bar of a specific type, e.g., a foo instance cannot have two bars of type A.

Given an instance Foo foo, I can get a specific type of bar using foo.GetBar<A>(), foo.GetBar<B>(), etc, but calling the bars->get<T>() function is slow and expensive. Therefore, I'm thinking of caching the result of GetBar() so that subsequent calls can return immediately without querying the pool again.

Now this is what I came up with: I created a static variable inside the member function to store the value of bar, it is only initialized and assigned value once.

template<typename T>
T& GetBar() {
    static T bar {};
    if (bar == T {}) {
        bar = bars->get<T>();  // a very slow function
    }
    return bar;
}

The problem is that, using the static keyword, this variable is now shared across all instances of Foo. If I try to get a bar of type A from different instances, they would return the same result.

Foo foo1;
Foo foo2;
foo1.AddBar<A>(1);
foo2.AddBar<A>(2);
foo1.GetBar<A>();  // returns a bar (type = A, value = 1)
foo2.GetBar<A>();  // returns the same bar with value 1, not 2

How can I cache every bar of type T inside the class and prevent it from being shared by other instances? I don't know how to store generic types as member variables, besides, storing each type T of bar can be a huge mess.

Edit: I know it'd be much easier to cache the result outside the class, on the caller's side. I'm just curious if there's an elegant way of caching inside the class.

Edit2: bars is a pointer to a registry pool, whose type is a complicated data structure, not a raw list or array. To clarify, I'm using the EnTT library to integrate entity-component-system into my application, but not sure how the pool is maintained internally in details.

Edit3: if you wonder what ABCs are, conceptually, these types are not known at compile time. but need to be determined at runtime. In fact, they are just many other class types I implemented, so I can also hardcode them into the Foo class, in which case I probably should use the factory pattern along with a scripting language for automatic code generation, but that would beat the purpose of using generics in the first place.


Solution

  • While writing a mockup, with the idea of n. 1.8e9-where's-my-share m., for your "complicated registry pool" I wrote the actual could be implementation of Foo. I left in there Foo only to also give some suggestions. If you want so have more than one variable of one type you would have to change the value type of the map of course, like from std::any to std::vector<std::any>. Otherwise please clarify your question more.

    #include <iostream>
    #include <string>
    #include <map>
    #include <any>
    
    struct Pool {
        template<typename T>
        void emplace(T x) {
            this->elements_.insert_or_assign(typeid(T).hash_code(), std::make_any<T>(x));
        }
    
        template<typename T>
        T& get() {
            return std::any_cast<T&>(elements_.at(typeid(T).hash_code()));
        }
    
    private:
        std::map<std::size_t, std::any> elements_;
    };
    
    class Foo {
    public:
        Foo(Pool& pool): bars_(pool) {}
    
        void AddBar(int x) {
            return bars_.emplace<int>(x);
        }
    
        template<typename T>
        T& GetBar() {
            return bars_.get<T>();  // a very slow function
        }
    private:
        Pool& bars_;
    };
    
    int main(){
        Pool pool;
        pool.emplace(4.3); pool.emplace(std::string("a value"));
    
        Foo foo1(pool);
        foo1.AddBar(3);
    
        std::cout << foo1.GetBar<int>() << "\n";
    }