The key is "small and well chosen set of base classes" for your exceptions. This is where you got the most benefit, and you probably know it. Without exceptions, you can have a class that represents well defined state such as either success or an error code, with the error code being well defined, returning an object of this class for all methods that need to return success/failure codes, and requiring that it be handled, so compilation fails if callers ignore the return value.
What's great about this is I might call a method that simply concatenates two strings, that is all it does, and I know this won't fail(OOM can always happen, but good luck recovering from that), so I don't return a status. I can call this method assuming no failure and no boiler plate is needed. With exceptions, there's always that possibility that an exception can be thrown. What if I call this method from code that needs to close resources? Great, now I absolutely must have RAII for cleanup, which is more complicated than just doing close(x). Also, what are you going to do when using 3rd party code with its own base exception class? Now, at the upper layers of your code, you have to have a handler for each base class. Without exceptions, someone might have their own status object they return, and I'll need to translate that, else my code won't compile. Your code will compile even though you might not handle that 3 party base exception class.
Of course, most times code just propagates the error up so that adds to boiler plate without exceptions.
I think the talk of using exceptions vs returning a status is a red herring. They have their positives and negatives, and we can argue this until the cows come home. The most important thing is to use a set of well defined success/error codes, whether this comes from exceptions or a returned status isn't really important. I've seen codebase with C++ exceptions working just fine, and I've seen c++ code with no exceptions, and it worked well. The commonality between these two cases is .... well defined error codes.
What's great about this is I might call a method that simply concatenates two strings, that is all it does, and I know this won't fail(OOM can always happen, but good luck recovering from that), so I don't return a status. I can call this method assuming no failure and no boiler plate is needed. With exceptions, there's always that possibility that an exception can be thrown. What if I call this method from code that needs to close resources? Great, now I absolutely must have RAII for cleanup, which is more complicated than just doing close(x). Also, what are you going to do when using 3rd party code with its own base exception class? Now, at the upper layers of your code, you have to have a handler for each base class. Without exceptions, someone might have their own status object they return, and I'll need to translate that, else my code won't compile. Your code will compile even though you might not handle that 3 party base exception class.
Of course, most times code just propagates the error up so that adds to boiler plate without exceptions.
I think the talk of using exceptions vs returning a status is a red herring. They have their positives and negatives, and we can argue this until the cows come home. The most important thing is to use a set of well defined success/error codes, whether this comes from exceptions or a returned status isn't really important. I've seen codebase with C++ exceptions working just fine, and I've seen c++ code with no exceptions, and it worked well. The commonality between these two cases is .... well defined error codes.