You could use a few vectors or other container objects, which is a tradeoff - it gets a little bit more comfortable to spot/debug OOB issues (but valgrind and sorts can also help with the occasional problem). In exchange you are required to structure your data and code in a particular way to use these structures, which is a drawback. Another drawback is slower compile times (significantly so with std:: containers -- don't have a lot of experience with hand rolling C++ abstractions).
I often prefer having globally allocated (dynamic) arrays, which are unproblematic with respect to OOB or memory/allocation safety. I use a simple macro to wrap allocation of arrays to remove the boilerplate. It becomes like "realloc_array(pptr, FooType, 42)" or even "realloc_array(pptr, 42)". Local allocations are more often not easily dealt with using Memory Arenas, which is superior to alloc/free in constructors/destructor.
I even prefer using plain pointers with separate size variables for dynamic arrays. That's because "size :: array" isn't a 1::1 relationship. More than 50% of the time, I have more than one array that applies to the same size variable. Sometimes, there are multiple index/size pairs that apply to a single (or multiple) arrays.
I prefer staying with C because it keeps me focused. Sure, sometimes I wish for the occasional fancy feature, but then taking the time to think about a clean approach that solves the problem with limited features often benefits code quality and readability.
the number of bugs that the compiler has saved me by e.g. having a simple Id<T> type tag instead of ints as used by every C library under the sun is absurd.
How do you go about Id<T>? The way I see that something like this can work out and give small advantages is that T would be an abstract type (a phantom or tag type, or whatever C++ guys are calling it). And all arrays that are meant to be indexed using Id<T> (while the array element types could be whatever) would depend on this type and have to be implemented using a special wrapped array type with overloaded operator[].
I don't know, this might come with many disadvantages.
All the stuff goes out the roof when you're now dealing with start indices + slices, i.e. doing "pointer" arithmetics.
My general feeling is that this Id<T> approach might show problems similar to the idea of using typed units, like wanting to prevent the expression "1W + 3s" but then making it terribly cumbersome to do real work, e.g. "1W * 3s" which would be perfectly valid.
In one C project I resorted to using about 20-30 typedefs like:
typedef int Expr;
typedef int Symbol;
etc. And I used these typedef names in function signatures. Variables also were named like expr, parentExpr, symbol, etc. It worked wonderfully. I had maybe 1 mistake where I submitted the wrong "type" in 6 months. And I'm someone who makes a lot of stupid mistakes.in a very simplified way:
template<typename T>
struct Id { int value; };
template<typename T>
struct Identifiable { Id<T> id; };
struct MyEntity : Identifiable<MyEntity> { string name; void foobar(); };
> And all arrays that are meant to be indexed using Id<T> (while the array element types could be whatever) would depend on this type and have to be implemented using a special wrapped array type with overloaded operator[]. std::unordered_map<Id<T>, whatever>
works fine (or your container of choice... flat_map, boost::multi_index...).> It worked wonderfully. I had maybe 1 mistake where I submitted the wrong "type" in 6 months.
well, you're good, I remember when I introduced this in my code base, that caught a dozen bugs almost immediately - and I get prevented by the compiler to introduce more every couple months.
Sorry to be blunt, but I'm sure that C++ isn't the problem here.
How ironic that commercial vendors get crucified for C and C++ language extensions.
Sometimes I've found that it's just easier to stick with plain C that way you don't ever feel tempted.