0

How to determine array-length (#) at compile time from a complex code?

In my code, if I skimmed it, I can know how much array-size it is needed, but the related algorithm is scattering around many .h/.cpp.

(Sorry for an obscure title and introduction, I don't really know what the problem is called.)

A solid example (demo)

I want to create many type of relation 1:1 between many game objects.

Library code (static library)

GO.h (game-object): store pointer to another-one.

class GO{//game object
    public:
    static const int numRelation=10; //#
    GO* left[numRelation];  
    GO* right[numRelation];
    public: GO(){
        for(int n=0;n<numRelation;n++){
            left[n]=nullptr;
            right[n]=nullptr;
        }
    }
};

DB.h (Database): It is a utility. It stores index of relation in userIndex.

class DB{//database
    int userIndex=-42;
    public: DB(){};
    public: DB(int us){userIndex=us;}
    public: GO* getLeft(GO* g){return g->left[userIndex];}
    public: void createRelation(GO* g1,GO* g2){
        g2->left[userIndex]=g1;
        g1->right[userIndex]=g2;
    }
};

DBGenerator.h (Database-Generator) :
It is a central system to help cooperation of many other sub-system.
It generates userIndex for DB.

class DBGenerator{//quite singleton
    int indexCounter=0;
    public: DB generate(){return DB(indexCounter++);}
};

User code (game-specific)

CageManager.h has two 1:1 relation : hen-food and hen-cage.
FamilyManager.h has one 1:1 relation hen-egg.

enter image description here

class CageManager{
    DB dbHenFood;   
    DB dbHenCage;
    public: CageManager(DBGenerator* gen){
        dbHenFood=gen->generate();
        dbHenCage=gen->generate();
    }
};
class FamilyManager{
    DB dbHenEgg; 
    public: FamilyManager(DBGenerator* gen){
        dbHenEgg=gen->generate();
    }
};

The userIndex will be reserved like this :-

enter image description here

I can add relation and query e.g. :-

int main() {
    DBGenerator commander;
    CageManager m1{&commander};
    FamilyManager m2{&commander};
    GO* hen=new GO(); 
    GO* egg=new GO();
    m2.dbHenEgg.createRelation(hen,egg);
    GO* henGet=m2.dbHenEgg.getLeft(egg);
    if(henGet==hen)std::cout<<"correct";
    if(henGet!=hen)std::cout<<"wrong";
    return 0;
}

Question

The above code works OK. (coliru demo, ideone backup)
However, as my game grows, I need more slot.
If the amount of slots exceeds 10, I have to edit the figure (const int numRelation=10) in GO.h manually.

Another trouble is that : if I want to create a new game,
I need to modify GO.h to ensure that it will be enough (but not too much - waste memory) for the new game-logic.

How to make such variable (const int numRelation) to always has a correct figure (3 in this test)?

Other note:-

  • I want to avoid heap allocation, so the size of array have to be fixed.
  • It is performance-critical, because the database is called excessively.
  • I don't know if there is an elegant way.
    From what I want, GO.h will probably have to include those two manager.h.
    It may break the whole architecture. (???)
javaLover
  • 6,347
  • 2
  • 22
  • 67
  • It seems like you are trying to write a "foundation" code in a top-down approach. This may leads to high coupled code. Try to think in entities and links instead of cages, families or whatever. Sorry for the self promotion, but I'm working on an [hierarchical entity-component system](https://github.com/csguth/Entity) that might be interesting for you to have an inspiration. – csguth May 15 '17 at 09:05
  • @csguth Thank. It seems that you are correct. However, I can't really find an elegant way to do it. By the way, do your approach (about relation) better than those proposed in http://stackoverflow.com/questions/43383088? – javaLover May 15 '17 at 09:11

1 Answers1

1

As a general rule, constexpr finishes execution before link time. So you cannot have a constexpr term whose value depends on things in different compilation units.

Your singleton value of "number of relations" thus cannot be calculated at compile time, nor can the index of each relation be calculated at compile time.

As a first attempt, we can get around this by asking each relation to pick a number, then verify there is no conflict. If checked at compile time, this would generate a constexpr value that depends on different compilation units; thus impossible.

We can check for "is the index we picked in bounds" at compile time. We can check "is there a collision" at runtime.

If we do this at static initialization time, and we require that a single line be inserted at main, we can detect if there is wasted space as well.

Replace the generation of DBs with a requirement that each relation claim an index. Have the indexes be checked to be in bound. At runtime, at the start of main, validate that there are no holes in your index table. This keeps relations decoupled from each other (they don't have to know about each other).

Now, this won't work if you expect to have an engine reused in multiple different projects with a different number of relations, because one will develop holes that the other doesn't want.

We could back up our requirement; remove the need to manually write constexpr indexes, and have those also generated at startup (like your code does). We simply add in runtime checks for "am I beyond bounds" and another check for "did we waste space" injected in helper at the start of main.

Now we get decoupling, no wasted indexes, and no manual index management. There is a trivial startup cost (which basically matches what you pay with the above code).

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Let me summarize it to make sure I understand it right : First paragraphs mention about impossibility to use `constexpr` (It is really useful!). Next, you mentioned about a solution that use manual index, and its problem when apply to different projects. Finally, you proposed another solution that is similar to me but has little improvement : add some run-time check. (sorry, I didn't mention that I already did it in real code) It looks like you are implicitly saying that my code is already quite good, and I should stop worrying about it now. – javaLover May 16 '17 at 01:58
  • 1
    @javaLover I'm saying that I don't believe what you want is possible within a pure C++ solution. So I proposed some compromises. – Yakk - Adam Nevraumont May 16 '17 at 02:19
  • It is a little off topic, but do you know what are resource/link/keyword/technique that I should study more? I am stalking about these keywords "custom allocator", "database", "custom scripting to modify c++ code", "entity-relationship" and "x-macro". Do I miss something important (besides prematurely optimize = evil)? – javaLover May 16 '17 at 03:49
  • It is nice that you responded with some negative statement rather than just say yes ("I'm saying that I don't believe..."). It made me trying to find other workarounds (totally different way), and I finally found it. Thank a lot. XD – javaLover May 16 '17 at 11:20