Boost logo

Boost :

Subject: Re: [boost] [outcome] To variant, or not to variant?
From: Andrzej Krzemienski (akrzemi1_at_[hidden])
Date: 2017-06-02 07:44:32


2017-06-01 14:15 GMT+02:00 Peter Dimov via Boost <boost_at_[hidden]>:

> Andrzej Krzemienski wrote:
>
>> > > // *operator->()
>> >> > T& operator() & noexcept;
>> >> > T const& operator() const & noexcept;
>> >> > T&& operator() && noexcept;
>> >> > T const&& operator() const && noexcept;
>> >>
>> >> This is operator*, right? This is technically narrow, but without >>
>> essential benefits of direct narrow contract, as I tried to explain in >>
>> another thread. I disagree.
>> >>
>> >
>> > What essential benefits are lost?
>>
>>
>> I tried to outline my reasoning in this post:
>> http://boost.2283326.n4.nabble.com/outcome-narrow-contract-
>> wide-contract-and-value-if-td4695003.html
>>
>> In short, when the narrow contract is directly in the library's
>> interface, I have a place where I can put BOOST_ASSERT(), or similar code
>> for assisting instrumented builds.
>>
>
> 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.

> Do we agree that returning nullptr from operator-> is good for informing
> the static analyzer and the optimizer that a value is present?
>
> result<X> r;
>
> // ...
>
> r->x; // same as __builtin_assume( has_value() )
>
> X x = *r; // ditto
>

I cannot answer this question with certainty. It looks like it has the
potential to address the static-analyzer/UB-sanitizer use cases. On the
other hand, static analyzers are not ideal, and they get lost when the
reason for UB spans across to wide space. It is conceivable that putting an
ASSERT(), or some such, earlier (directly in `outcome` could help detect
UB, but your trick with operator-> would not. But I have no data to support
this.

> 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".

> 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"? IOW, would
your position be different if we were discussing a design of some bigger
library, like ASIO. In yet other words, do you object the same to
vector::operator[] allowing UB when over-indexed?

I fail to be convinced that "error handling library" should make different
decisions than any other library. It is my understanding that `outcome and
`expected` are intended to carry the information about unavailability of
resources, and similar conditions in the environment. In contrast, what we
are arguing about is what happens upon programmer using the library
incorrectly.

> 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. It is not robust to requesting the call X* to something you know not
to be an X and you are not preventing that.

> not introduce subtle bugs

In general, you cannot protect form users planting bugs in their logic.

> 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.
Although I am not sure that this is the library that should take this
responsibility. In my view, the library should provide information what the
invalid usages are, and there should be another tool (like static analyzer)
that does the job of minimizing damage.

>
> (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?

> and you can still have a mode in which you assert in op->, but it will not
> conform.)
>

But then I do not understand what you get in this compromise that operator*
has narrow- and operator-> has a wide contract.

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()`.

>
> 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. The different between the two is the following. In order for UB
to occur you require two things: a function, call it `f` with a narrow
contract, and an another function that calls `f` out of the contract. In
the ideal world you have functions with narrow contracts and nobody calling
them out of contract. And defining contracts, as formally as possible, help
users identify when they are violating the contract.

Not every design requires narrow contract. For instance, if we allowed only
variant-like inspection of `expected`, every usage makes sense and is wide
in contract. But the design choice that you first ask in which of the
states `expected` is in, and then "get_the_T` and `get_the_E` allows for
incorrect usage. Narrow contract simply reflects the fact that there is a
way to use this interface incorrectly. Narrow contract is not incorrect, it
simply reflects the fact that there might exist incorrect programs.

> It isn't, it never is.

No, I do not believe that UB guarantees something in run-time, or rely on
it.

> Defining the good things that we want to happen is good. Not defining them
> in the hope they'll occur is not.

I agree with this statement in isolation, but I think you misunderstood my
position about narrow contracts.

Regards,
&rzej;


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk