You can check manually, but we know programmers can't be trusted to do so correctly every time. There's an ocean of security vulnerabilities because of this, as out-of-bounds access of a raw array is undefined behaviour in C and C++.
It makes good sense to use at in debug builds especially, where you're less concerned about the performance penalty.
In C++ you can define a macro to select between at and [], and while this isn't exactly pretty, it does work:
#ifdef DISABLE_RANGE_CHECKS
#define AT_METHOD operator[]
#else
#define AT_METHOD at
#endif
#include <vector>
// ...
i = my_vector.AT_METHOD(my_idx);
> even there it looks questionable imo
Why? Its behaviour is pretty much strictly better than that of raw []. The only downside is a possible performance hit.
You could argue it is ok when you use exceptions for control flow (e.g. what people sometimes do in python), but even there it looks questionable imo.