2017-05-18 17:06 GMT+02:00 Niall Douglas via Boost
This seams to be implying something opposite: that empty can can be treated as less abnormal than error.
You're forgetting locality. Locally to the find loop, the valued or errored state returned by something() is the normal situation and you don't care which it returns. Locally to the find loop, failing to find what we are looking for is the abnormal situation.
The programmer looking at the find loop won't be thinking in terms of errors or success of something(), but rather that the flow of execution in the find loop is correct or incorrect.
I really should emphasise that this stuff, when used in practice, is nothing like as complicated as this thread of discussion is making it seem. Any programmer, even an undergrad, will look at that find loop and know exactly what it means and how it works intuitively.
Ok, I misunderstood your example (maybe because I am not an undergrad :), sorry. Indeed, it treate an empty state as the most abnormal.
It is my impression that the system of types in Boost.Outcome confuses two meanings of "empty"
option<T> -- either T or "empty" -- in this case "empty" looks like "just another state of T", as in boost::optional result<T> -- either T or an error or "empty" -- in this case it means "abnormally empty"
Am I right? If so, maybe you need two separate states "simply no T" and "abnormal situation"?
Now, my above observation is invalid.
Empty is *defaulted* to the most abnormal state by Outcome's default semantics. So if you call .error() on a valued Outcome, you get back a default constructed error type, no exception thrown. Same goes for .exception().
But if you call .error() or .exception() on an empty Outcome, you *always* get an exception thrown. Therefore, if you write your Outcome using code without state checks before observation i.e. you call .error() without checking .has_error() beforehand, you get a stronger, more abortive default action than if the Outcome were errored, valued or excepted.
You the programmer may wish to avoid the default actions, in which case you check state before access. You can then manually specify any other action you prefer.
The "more abnormal" solely refers to my design choice of default semantics only for the empty state. Reviewers may think those choices misguided or plain wrong, and might suggest better default semantics. For example, some might feel that any time one tries to observe state where the state is different, you should always throw an exception.
That would be a very conservative design choice and I'd disagree with it. But I would understand the rationale. I would also add that it is trivial to wrap an Outcome with replacement observer functions which change the default actions, or to customise the policy class to have different defaults.
My personal preference on this is that if you call `o.error()` before you have confirmed you actually have an error, you are doing something wrong. I would classify such situation as undefined behavior. But your choice fits into the scope of undefined behaviour: if program can do anything, it might as well return a default-constructed error_code. On the other hand, you do loose something when you chose something else than undefined behavior: the potential for such user bugs to be detected by static analyzers. But still, my initial concern remains somewhat un-addressed. I understand that .empty() is treated as the most ubnormal state. I accept your choice of throwing an exception upon a call to .exception(), I understand why someone might want to type: ``` if (o.has_value()) {} else if (o.has_error()) {} else if (o.has exception()) {} else {} ``` But I do not understand why in my program I would want to write: ``` if (o == tribool::unknown) {} ``` How is that better ina any way from `o.is_empty()`? Regards, &rzej;