javaLover
javaLover

Reputation: 6425

Create "custom typeid" for many types AND cache their sizeof()

There are many type of elements E0,E1,..., all of them derived from Element.

class Element{  public: int id=0; } //"Element" can be edited
class E0: public Element{ ... }
class E1: public Element{ ... }
...
class E999: public Element{ ... }

How to cache sizeof() for E0,E1,...?
It has to return the correct value [#2] later by "custom typeid" [#1].

class CCollection{
    template<class EX> EX* create(){ //(Edit)
        //all EX* is created by this function
    }
    /** user only pass EX*, never pass Element* */  (Edit)
    int toId(Element* element){//[#1] should replaced by template?
          //....... return id, e.g. 0 for E0, 1 for E2, 2 for E1, ...  
          // can be in any order, but must unique and packed ........ 
    }
    int sizeOf(int id){//[#2]
        //....... return size of EX
        // must consistent with toId() ,
        // e.g. sizeof(E0) for 0, sizeof(E2) for 1, sizeof(E1) for 2 ........ 
    }
    //....... other fields / functions, if necessary ............


}

How to implement this?

Requirement:

  1. Manually assign id-integer of E0,E1,E2,... is not allowed, because of low maintainability.
  2. User doesn't have to "register" E0,E1,E2,... to CCollection manually.
    When new type of Element, e.g. E1000, no modification have to be done.
    ..... i.e. CCollection should be "just work" for E1000.
  3. Highest performance, e.g. :-
    • Virtual calling should be avoided, except totally unavoidable.
    • Use constexpr if possible.
    • Static variable inside a function is not good.
  4. Don't modify code of E0,E1,E2,...

Flexibility in design

  1. If CCollection's functions are never called for a certain E-X,
    the management for E-X can be omitted (up to you).
  2. There is only 1 CCollection instance.

clue1: Template seems to be the most promising path, but I am not sure.

clue2: (Edit) This snippet I have planed may help :-

template<class EX> int toId(EX* element){//[#1] 
    // register something?
}

As you can guess, the real problem is far bigger than this,
but this is the only one missing jigsaw of my whole puzzle.

Although I wish for code, just a guide is really appreciated.

Upvotes: 1

Views: 746

Answers (2)

skypjack
skypjack

Reputation: 50540

If you can add an intermediate class between Element and each EX, here is a possible solution (with a minimal, working example):

#include<cstddef>
#include<memory>
#include<vector>
#include<cassert>

struct Element {
    static std::size_t counter;
};

std::size_t Element::counter = 0;

template<typename>
struct ElementType: Element {
    static const int type;
};

template<typename T>
const int ElementType<T>::type = Element::counter++;

struct E0: ElementType<E0> {};
struct E1: ElementType<E1> { int i; };
struct E2: ElementType<E2> {};

class CCollection {
    void ensure(std::size_t type) {
        if(!(type < sizes.size())) {
            sizes.resize(type+1, 0);
        }
    }

    template<typename EX>
    void ensure() {
        ensure(EX::type);
        sizes[EX::type] = sizeof(EX);
    }

public:
    template<class EX>
    std::shared_ptr<EX> create() {
        ensure<EX>();
        return std::make_shared<EX>();
    }

    template<typename EX>
    std::size_t toId() {
        ensure<EX>();
        return EX::type;
    }

    std::size_t sizeOf(std::size_t type) {
        ensure(type);
        return sizes[type];
    }

private:
    std::vector<std::size_t> sizes;
};

int main() {
    CCollection coll;
    assert(coll.toId<E0>() != coll.toId<E1>());
    assert(coll.toId<E0>() != coll.toId<E2>());
    assert(coll.toId<E1>() != coll.toId<E2>());
    assert(coll.sizeOf(0) == sizeof(E0));
    assert(coll.sizeOf(1) == sizeof(E1));
    assert(coll.sizeOf(2) == sizeof(E2));
    assert(coll.sizeOf(0) != coll.sizeOf(1));
    // ...
}

The problem in the code in the example arises if one tries to create instances of EX without using create.
Anyway:

  • You said it should never happen.
  • You can enforce the use of that function.
  • The CCollection class is designed so as it returns 0 from sizeOf if you do that.

That said, it can serve as a base for a more elaborated production code at least.

Upvotes: 1

Jarod42
Jarod42

Reputation: 217265

I think following doesn't break any of your rules:

struct CCollection
{
    template <typename T>
    std::size_t toId() {
        auto it = info.find(typeid(T));
        if (it == info.end()) {
            it = info.insert({typeid(T), {count++, sizeof(T)}}).first;
        }
        return it->second.id;   
    }

    std::size_t sizeOf(const Base& base) const {
        const auto& data = info.at(typeid(base));
        return data.size;   
    }

    std::size_t count = 0;
    std::unordered_map<std::type_index, Data> info;
};

Demo

Upvotes: 2

Related Questions