That is called an index. If you want it to be standalone, you can bundle it with the ArrayList.
Examples would be eg, `string_view` or `ArraySegment`. They hold some offset relative to a base allocation, and when we index the string_view or ArraySegment we're indexing relative to that offset.
It's not holding an "offset", it's a fat pointer, (ptr,len) or possibly (start,end)
You're imagining this as (string_ptr,offset,len) but that's 50% bigger for no practical benefit, you cannot unwind a std::string_view to get the string it's a view into, indeed there may never have been such a string.
But... This loses the reason people are using indices to begin with: because the borrow checker cannot track what they do.