Let's take one specific example -- connecting to a remote host. The loop that goes through the DNS returned IP numbers uses local error codes to try each in turn until one is accepted. If none are accepted it throws an exception so that the higher level application code can decide what to do about the connect failure. The exception ensures that all resources are properly cleaned on the way through.
The exception is used to guarantee that resources are properly de-allocated on the way back up to where the error needs to handled as it cannot be handled locally, probably not in the function that kicked off the connect attempt either, but there's no local exceptions to handle the case where the error is handled locally.
The use of the exception is more like a ROLLBACK on a database transaction together with error reporting -- it helps ensure correctness by backing out properly changes in state that were kicked off by the connect attempt. When the exception is caught the program state is exactly as it was when the connect attempt was first tried so we know we have good clean state to make another attempt or to move on and do something else. Backing out all of these other state changes is really hard when an error code needs to be handled non-locally -- and it's this non-local handling of errors that's so hard and error prone without exceptions.
So if you're only ever using error codes you'll far too easily make mistakes when the error needs to be handled non-locally, and if you're only ever using exceptions then it's going to be real ugly when the error is handled locally.
You need to use both if you want clean code that is going to work reliably (or you need massive engineering resources to fix all of the bugs you'll end up with).