I think the [answer of rwong above][1] already excellently highlights the issues.

I'll add my 002:

* [`size_t`][2], that is, a size that ...
  > can store the maximum size of a theoretically possible object of any type (including array).

  ... is only required for range indices when `sizeof(type)==1`, that is, if you're dealing with byte (`char`) types. (But, we note, [it can be smaller than a ptr type][3]: 
* As such, `xxx::size_type` could be used in 99.9% cases even if it were a signed sized type. (compare [`ssize_t`][4])
* The fact that `std::vector` and friends chose `size_t`, an **unsigned** type, for the size and indexing is [considered by some][5] to be a design flaw. I concur. (Seriously, take 5 minutes and watch the [lightning talk CppCon 2016: Jon Kalb “unsigned: A Guideline for Better Code"][5].)
* When you design an C++ API today, you're in a tight place: Use `size_t` to be consistent with the Standard Library, or use (a *signed*)  **`intptr_t`** or `ssize_t` for easy and less bug prone indexing calculations.
* Don't use int32 or int64 - use `intptr_t` if you want to go signed, and want machine word size, or use `ssize_t`.

To directly answer the question, it is not entirely an "historical artefact", as the theoretical issue of needing to address more than half the ("indexing", or) address space *must* be, aehm, addressed somehow in a low level language like C++.

In hindsight, I, *personally*, think, it **is** a design flaw that the Standard Library uses unsigned `size_t` all over the place even where it does not represent a raw memory size, but a capacity of typed data, like for the collections:

* *given C++s integer promotion rules* ->
* unsigned types just don't make good candidates for "semantic" types for something like a size that is semantically unsigned.

I'll repeat [Jon's advice][6] here:

* Select types for the operations they support (not the range of values). (*1)
* Don't use unsigned types in you API. This hides bugs with no upside benefit.
* Don't use "unsigned" for quantities.(*2)

(*1) i.e. unsigned == bitmask, never do math on it (here hits the first exception - you may need a counter that wraps - this must be an unsigned type.)

(*2) quantities meaning something you count and/or do math on.


  [1]: https://softwareengineering.stackexchange.com/a/338107/6559
  [2]: https://en.cppreference.com/w/cpp/types/size_t
  [3]: https://stackoverflow.com/questions/1464174/size-t-vs-uintptr-t
  [4]: https://www.gnu.org/software/libc/manual/html_node/I_002fO-Primitives.html
  [5]: https://www.youtube.com/watch?v=wvtFGa6XJDU
  [6]: https://youtu.be/wvtFGa6XJDU?t=358