Proposal: C++: push more logic to runtime
This is just an idea that I thought I'd ask about before investing too much time in expanding it further.

It seems to me that the runtime could be allowed to do quite a lot more that it is doing and allow more fine-grained end-application configuration. And it can also simplify the code generator.

## C++ standard detection

For example, rather than adding options to the compiler to enable C++14 support here:

```cpp
      case UniqueAndRawPointers =>
        s"std::unique_ptr<std::vector<$cppElType>>(new std::vector<$cppElType>())"
        // TODO: C++14 with std::make_unique
    }
```

The (default) runtime can supply:

```cpp
namespace kaitai {

template<typename T>
std::unique_ptr<std::vector<T>> create_vector() {
#if  __cplusplus == 201103L
    return std::unique_ptr<std::vector<T>>(new std::vector<T>());
#else
    return std::make_unique<std::vector<T>>();
#endif
}
} // namespace kaitai
```
and the compiler can be just:
```scala
s"kaitai::create_vector<T>()"
```

## Reservation

While this can be done in the compiler, it also makes this more readable as it's just normal C++:

```cpp
template<typename T>
std::unique_ptr<std::vector<T>> create_vector(size_t reserve) {
    auto vec = std::make_unique<std::vector<T>>();
    vec.reserve(reserve);
    return vec;
}
```

> Example: this appears twice as fast for a large array. A user can opt to pre-size the array rather than reserving, as long as they can tolerate the array being underfilled if the stream ends prematurely.
> 
> ```cpp
>     const int l_substructs = ((64 * 1024) * 1024);
>     m_substructs = new std::vector<uint32_t>(l_substructs);
>     for (int i = 0; i < l_substructs; i++) {
>         (*m_substructs)[i] = m__io->read_u4be();
>     }
> ```

## Custom implementations

This also allow the user to implement their own construction functions by adapting their runtime, transparently to the generated code. For example, if the default runtime implementation is

```cpp
template<typename T>
std::unique_ptr<std::vector<T>> create_vector() {
    return std::make_unique<std::vector<T>>();
}
```

The user can provide their own version for a specific type. In particular, they can provide a custom allocator, which might be especially handy for types like uint8_t where they can pre-allocate a pool. Or they can implement memory usage tracking.

```cpp
template<>
std::unique_ptr<std::vector<T>> create_vector<uint8_t>() {
    return std::make_unique<std::vector<T, ByteAllocator>>();
}
```

You could even provide a template over the parent type, so that the user can also choose to only provide custom implementations for certain structs:

```cpp
template<typename PARENT_STRUCT, typename T>
std::unique_ptr<std::vector<T>> create_vector() {
    return std::make_unique<std::vector<T>>();
}
```

## User contexts

This would also allow users to inject their own logic into the runtime as they want, without the compiler needing to be aware, and without the default implementation needing to be anything non-trivial, so mostly it optimises away in the default case.

For example, a user can log global vector creation counts:

```cpp
static size_t vector_creations = 0;

template<typename PARENT_STRUCT, typename T>
std::unique_ptr<std::vector<T>> create_vector() {
    vector_creations++;
    return std::make_unique<std::vector<T>>();
}
```

Passing the root kstruct in would allow the user to have a context per top-level struct.

And if the function took the parent, it could walk the tree for the struct location in the hierarchy:

```cpp
template<typename PARENT_STRUCT, typename T>
std::unique_ptr<std::vector<T>> create_vector(kaitai::kstruct* p__parent)
    return std::make_unique<std::vector<T>>();
}
```

And the struct creator is similar:

```cpp
template<typename PARENT_STRUCT, typename ROOT, typename T>
std::unique_ptr<T> create_struct(kaitai::kstream* p__io, PARENT_STRUCT* p__parent, ROOT* p__root) {
    return std::make_unique<T>(p__io, p__parent, p__root);
}
```

It seems that such a runtime would still only be a small handful of functions by default, and by default they'd just collapse into what we currently have.. Perhaps simply these few functions:

* vector create/delete
* struct create/delete