First of all, thank you for the response.
You said that you have a function that previously was creating a temporary file on disk (and could generate eaccess) but now it creates it in memory (and can generate enosys instead).
Correct me if I am wrong, but, from what I can gather, you do not want to have a function creating a temp file on disk, nor in memory.
What you want is a facility
* for creating a temporary file somewhere (for example, using a hard-coded logic: first try in memory, if it fails - on disk, or there might be a hint-parameter as to where create it)
* that could report an error (for example in the form of expected<fd, TError>).
The latter is the most interesting part. As I described in my post, I would go for "nested error structure with increasing degree of details":
1st level: I would hazard a guess, for majority of callees "failed/succeeded" is enough - we have expected<> for this.
2nd level, by looking in the man page of the open() syscall, there is a couple of broad groups of errors: AccessFailed, BadArguments, maybe something else - this information might be necessary for a finer-grained handling.
3rd level: precise, primarily debug-only, OS-specific errno code:
struct TError {
enum TStatus { BadArg, AccessFailed, ... }
TStatus Status;
std:::string Args; // used when Status == BadArg
int SysErrno;
}
As I wrote in my post, you can enhance this approach however you want, for example place "sub-errors" in a variant<>.
So, with this approach callees have the freedom to choose the level of details they want to know about.
And everything is checked by a compiler: consider the situation when a callee had a switch over TStatus, and then somebody adds a new element in the enum - you will receive a warning from compiler (given you build it together) about not handled case in the switch. This warning is perfectly fine, because, by having the switch, callee "said" that it is interested at this level of details, but somebody changed it - with all likelihood it is better to look at the switch closer.