Andrzej Krzemienski wrote: ...
That's what I was thinking - that you want to place an assert there - I just wanted to confirm that this is the only objection.
It is not _the only_ objection. I thought that this one would be the easiest to communicate across.
What are the others?
I can understand the desire to assert() inside, but at the same time, view the idea of returning a X* to something that is not an X ....
Here, the first disagreement. The way I see it is that the blame is on the user side. I would put it, "requesting the call X* to something you know not to be an X is misguided".
Where is the disagreement? The blame is on the user side. This does not change what actually transpires.
as monumentally misguided in an error handling library.
A question to you. Is your verdict affected by the fact that this is "an error handling library" or that this is a "vocabulary type"?
No, this being an error handling library just makes the argument easier.
In yet other words, do you object the same to vector::operator[] allowing UB when over-indexed?
Possibly, today. In the past, not. It depends on whether the optimizer is good enough to avoid the repeated bounds check, and because of aliasing concerns, it may not be likely that it will ever be able to optimize it out in this case. vector::operator[] accesses occur in tight inner loops, the check is hard to optimize out, so we swallow the UB. Doesn't mean we have to like it, it's a necessary evil here.
I fail to be convinced that "error handling library" should make different decisions than any other library.
It shouldn't, I already said the operator-> for smart pointers should behave the same.
The goal here is to help people write robust code,
Here is the second disagreement. That is, I do agree with you (and I guess everybody agrees) that this is always the important goal: help people write robust code. I just fail to see how the decision to assign *just any* semantics to something that is a misguided decision (I mean "requesting the call X* to something you know not to be an X") helps people write robust code.
"Robust" is not the same as "correct". Robust code means code that doesn't misbehave when faced with unforeseen circumstances; having a logic error is such one unforeseen circumstance. Nobody is perfect.
not introduce subtle bugs
In general, you cannot protect form users planting bugs in their logic.
And I do not. Accessing a null pointer crashes. I however can mitigate the effects of their bugs, which is what I'm doing.
and security vulnerabilities by allowing them scribble all over the stack.
So, is this your goal? In case the programmer does not know what he is doing (that he is using the library incorrectly), the library itself should take actions to minimize the damage? I am somewhat convinced by that.
Yes, exactly. Here you have a union of an arbitrary type and std::error_code. std::error_code contains a pointer to a type with a vtable. Allowing the ability to write arbitrary data over that pointer is a security concern because this could lead to arbitrary code execution.
(Note that with the above spec, you can still assert in op* and it would be conforming,
Do you mean that you are ok with operator* having a narrow contract?
It already has. I have deliberately left it implicit though.
And we should also bare in mind Niall's opinion, that if he agrees to a narrow contract, such function should spell longer than `value()`.
Yes, there is that.
The root of our disagreement is the idea that undefined behavior is good because it supposedly allows you to do this or that.
It is not in this that I see the root of our disagreement. In fact, I do not yet see where this root is. What I fight for is not an UB but narrow contracts.
Traditionally the two are synonyms in C++. If you have a narrow contract, the behavior on contract violation is undefined. Can't have one without the other.