TartanLlama / expected

C++11/14/17 std::expected with functional-style extensions

Home Page:https://tl.tartanllama.xyz

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Is expected::error() ment to throw if it contains a value.

ashley-b opened this issue · comments

Sorry if this has already been answered, but I found this odd behaviour.
When I do the following test

tl::expected< std::size_t, std::errc >; s(20);
fmt::print("s.value() {}\n", s.value());
fmt::print("s.error() {}\n", s.error());

I get

s.value() 20
s.error() 20

I would have expected it to throw a bad_expected_access equivalent, like accessing value() when is set to unexpected.

As a follow up, I found the original author implementation and there solution is to use assert's to safe guard this.
https://github.com/viboes/std-make/blob/b498cd1c9468445781afdd3a9eafad9cb2efb96c/include/experimental/fundamental/v3/expected2/expected.hpp#L393-L412

According to the documentation, it is undefined behaviour. Other instances of undefined behaviour are to use operator* or operator-> on an error value (unlike value() which throws).

I agree with you; the behaviour should retain memory safety. There is no performance benefit to leaving this as undefined behaviour, because in practice correct code will look something like this:

tl::expected e = ...;
if (!e.has_value()) { ... e.error() ... }

Suppose that assert(!has_value()) were added in tl::expected::error(). Since an optimizing compiler will inline error(), it can see that the assertion is redundant and will completely optimize it out. And so in the majority of cases, only incorrect code would incur the cost of the assertion. Of course it's possible to construct artificial examples in which that's not the case. But even if the compiler doesn't optimize out the assert, the boolean that records whether the expected is a value or an error will in practice be in L1 cache, so there will not be extra cache misses.

For our use of tl::expected in Zcash, I added a patch almost identical (but also covering operator* and operator->) to the one linked above. I intend to file a PR for that here.

Note that this change does not introduce an incompatibility with any version of the std::expected proposal, because an assertion error is a valid refinement of undefined behaviour. Nor would it in practice lead to reliance on the assertion, because people don't want their programs to terminate with assertion failures, and it's still perfectly clear that such a program is incorrect.

@ashley-b is #117 sufficient to fix this from your point of view?