Exceptions should be for exceptional control paths, meaning, unless there is an external error beyond the program's control (users jamming an abort button, files being corrupt that shouldn't be corrupt, etc), it should operate through the non-exceptional paths. Zero-cost EH actually cements this idea further in stone by making exceptional paths extremely expensive in exchange for making non-exceptional paths practically free.
However, a library implementer actually shapes the client's idea of what should not happen in a regular control flow. If a library function to open a file can throw a FileNotFound error, then that means the client should not attempt, under any cases they can control, to open files that aren't found. They shouldn't be trying to open files left and right willy-nilly and relying on the exception-handling system to discover what ones exist and what do not.
That said, the library implementer also has a responsibility in this case. Since they decided that opening a file that doesn't exist is an external input error which throws an enormously expensive exception, and therefore not something that should ever happen in ordinary circumstances, I would say it is also their responsibility to make sure the client can easily avoid making such an attempt through the normal, non-exceptional control paths.
So the library might provide, say, a can_open method that is intended to be called prior to attempting to use the open method (unless the client himself requires the file to be there to operate normally, at which point they can just call open directly), and the fact that the open method can throw would be reserved for those very extraordinary circumstances where the file might be deleted between checking to see if we can_open it and then trying to open it. These should be very exceptional circumstances.
Library Design
From the library designer's perspective, whether an open function should throw or return a success/failure status is going to be based on anticipated usage patterns.
Scenario A: If you anticipate that your library will be used in cases where it's a very common occurrence for a file being requested to be opened to not exist, returning a status code makes much more sense as opposed to throwing since a file not existing would be a common occurrence.
Scenario B: If you anticipate that your library will be used by people who often require a specific file to be there to operate normally, like a video game requiring a "game.dat" file to be there for it to run properly, then it makes more sense to design an open function which throws when the file isn't there and provide something like a can_open method for people in the Scenario A group above which would be the rare case demographic.
Here it's not a big deal either way and kind of a toss up, but try to design towards the common use case if you can anticipate it.