You've compiled with _LIBCPP_HARDENING_MODE_FAST, which still adds some extra checks not required by the standard.[1] You can also tell it's nonstandard because it doesn't really throw out_of_range, it just traps.
> Fast mode, which contains a set of security-critical checks that can be done with relatively little overhead in constant time and are intended to be used in production.
> Using std::span as an example, setting the hardening mode to fast will always enable the valid-element-access checks when accessing elements via a std::span object, but whether dereferencing a std::span iterator does the equivalent check depends on the ABI configuration.
It does, on explicitly bounds-checked accessors like .at, which span is gaining for C++26.
But you originally implied using span was sufficient, you didn't mention LLVM's libc++ hardening. (You even mentioned iterators which, I just quoted, might not be bounds-checked on fast mode either.)
> It does, on explicitly bounds-checked accessors like .at, which span is gaining for C++26.
When I said "the standard doesn't require this" I clearly was not referring to C++26, which does not even exist yet. In any case, I'm not sure what the point of this pedantry is. I'm pretty sure the point was clear.
> But you originally implied using span was sufficient, you didn't mention LLVM's libc++ hardening.
Because this isn't LLVM-specific, every major STL has bounds checking. You just gotta enable it for your toolchain. Sorry I didn't list every single flag, I guess?
> (You even mentioned iterators which, I just quoted, might not be bound-checked on fast mode either.)
Which is why I had _LIBCPP_ABI_BOUNDED_ITERATORS, right? I'm not on HN to write comprehensive documentation for every toolchain, I'm just writing a quick tip for people to look into.
All this pedantic quibbling over "this isn't required by the standard by default" is just pointless arguing for the sake of arguing on the internet. For all the performance freaks who really care about this: no language I know of guarantees optimizations in the standard, so if you're relying on optimized performance, you're already doing nonstandard stuff.
And practically every major compiled language you love or hate has a way to enable or disable bounds checking, letting you violate their "standard" one way or another. D itself has -boundscheck, C++ has toolchain-specific flags, Go has -gcflags=-B, etc...
So your first answer to being told your initial suggestion is insufficient for bounds-checking was to share a godbolt link without elaborating on where the checking was actually coming from; and when I elaborate, for other readers' sake, not yours, on your solution and other comparable ones, you get defensive and repeatedly call me pedant. Ok, but you know, these discussion are for everyone else to read and maybe learn something too, not just us.
As for the bounds-checked accessors, I mentioned them because they already exist in current C++ for other collections, they're coming to the one you suggested using, and I thought them relevant to a discussion about C++ lacking spatial safety.
I've used vendor-specific C++ compilers with no bounds checking and a barely conforming stdlib, so by your logic C++ has zero bounds checking... Defaults matter!
> Fast mode, which contains a set of security-critical checks that can be done with relatively little overhead in constant time and are intended to be used in production.
> Using std::span as an example, setting the hardening mode to fast will always enable the valid-element-access checks when accessing elements via a std::span object, but whether dereferencing a std::span iterator does the equivalent check depends on the ABI configuration.
1: https://libcxx.llvm.org/Hardening.html