Bjarne's guidance has always been to use exceptions only in truly exceptional circumstances. A function that might occasionally fail on bad user input could instead return a std::optional and be marked noexcept (assuming it really doesn't throw). A function that fails to allocate memory, on the other hand, is truly exceptional, so throw std::bad_alloc.
The STL unfortunately doesn't always follow this approach, and tends to overthrow.
Signaling why something failed without exceptions is still unnecessarily tricky in C++. Not because it can't be done, but because there's no standard guidance on how to do it. There are 3,385 ways to do the job, so in many cases it's better simply not to.