Vicente J. Botet Escriba wrote:
Peter, I not be against an implementation that returns nullptr of operator-> as far as this is not documented and something the user could use. If returning a nulptr is the best thing that we can do today in order to help the current tools to catch UB, why not. I don't agree on documenting it, because this could make some user code more complex. When you have a narrow contract you know that the check must be done before. If you document that it the user can be tempted to check it.
The expression `r->x` still has a narrow contract under my formulation. You (and the static analyzer) still know that a check needs to be done. It's just specified differently because the real world consequences of this specification are more in line with the semantics I want to express.
Well operator-> is particular in some way as the user doesn't use to use it as x.operator->().
Yes, `r.operator->()` is now defined, which some consider desirable. Presumably, if one writes `auto* p = r.operator->();`, one is aware of what one's doing. I wouldn't presume that this code is a logic error without a check, and neither should a static analyzer.
Consider for a moment that some compiler manage better UB when we do a check and assert for unreachable code (as other are suggesting). Requiring a nullptr as result will forbid this implementation, isn't it?
No, it doesn't. This formulation just lifts the undefined behavior in `r->x` from the library into user code, and what happens there depends on the compiler. g++ takes care to trap on null pointer access, clang++ optimizes out the checks under the assumption that null pointer accesses don't occur. The balance here is hard to strike, because there are security implications of just allowing `p->m = ...` to write over the error; at the same time, there's an argument to be made to optimize correct programs more at the expense of incorrect programs. The main feature of my formulation here is that `r->x` and `p->x` (where p is a raw pointer) elicit a consistent response from the compiler when they invoke UB, and are therefore covered by the same optimization decisions or command line switches.