Boost logo

Boost :

Subject: Re: [boost] expected/result/etc
From: Michael Marcin (mike.marcin_at_[hidden])
Date: 2016-02-12 00:02:47


On 2/11/2016 5:20 PM, Emil Dotchevski wrote:
> On Wed, Feb 10, 2016 at 11:19 PM, Michael Marcin <mike.marcin_at_[hidden]>
> wrote:
>
>> On 2/9/2016 3:16 AM, Niall Douglas wrote:
>>
>>>
>>> Something missing from the discussion so far is that
>>> expected/result/outcome MUST throw exceptions! If you try to fetch a
>>> value from a result and it contains an error, there is no alternative
>>> to throwing an exception. This fact is why I don't worry about static
>>> function initialisers and just go ahead and use
>>> error-code-via-system_error throwing constructors, ultimately you
>>> need try...catch in there one way or another.
>>>
>>>
>> There most certainly is an alternative!
>> I certainly don't want this behavior.
>>
>> There's no way that accessing the value of a result<T> without checking
>> for an error is not a programming error.
>> It should be a precondition that to access the value it needs to not
>> contain an error.
>> It should *not* be throwing an exception because you cannot fail to
>> satisfy the postcondition until you meet the preconditions.
>>
>> Undefined-behavior is the appropriate specification for accessing a value
>> from a result that contains an error.
>
>
> It depends what's your goal. If you want to avoid logic errors in error
> handling code (as you should), then you don't want accessing the result
> without checking for error to be undefined behavior.
>
> Consider the following C code:
>
> int * p=(int *)malloc(sizeof(int));
> *p=42;
>
> Of course the correct code is:
>
> int * p=(int *)malloc(sizeof(int));
> if( !p )
> return error;
> *p=42;
>
> Niall's motivation is to make code like this safer without resorting to
> exceptions. But the best way such code is made safe is by using exceptions.
> In C++ you can write:
>
> int * p=new int;
> *p=42;
>
> without invoking undefined behavior, because the compiler will
> automatically generate code that effectively does:
>
> int * p=new int;
> if( !p )
> return error;
> *p=42;

I disagree with your premise that throwing exceptions avoids logic
errors or makes code "safe".

To borrow from Andrzej's blog:

--
One thing that I want to point out is that just checking every possible 
“invalid function input” and throwing an exception on it does not 
necessarily make the program more correct or safe. It only prevents 
crashes, but not bugs. Bugs, in turn, are a sort of UB on the higher 
level of abstraction.
--
You likely routinely use "unsafe" code that can lead to UB in the 
presence of programming errors.
Do you use std::vector operator[] in your code?
Do you use std::vector iterators?
Maybe you always use std::vector at() to access elements.
If so then I understand your reluctance to deal specify UB.
Writing high performance code requires you to *think*.
In C++ we embrace UB when it is "The Right Thing".
You can certainly add a debug layer to result that can help diagnose 
problems (mark if the result was tested for containing a value or error 
is some way and assert if it was accessed without such a test).
Much as Microsoft's STL debug iterators are a helpful tool.
But I don't want to pay for them in retail builds.
There's no way I'm going to throw an exception because a raytrace hit an 
intersection limit when I need to maintain a consistent 90hz stereo 
rendering or risk making my users literally ill. Still it's an error and 
needs to be handled gracefully.
If I can't the interface and implementation I want and need out of a 
standard or pseudo standard library then I'll have to roll my own but 
it's a shame how often I have to do that. It's a large part of why STL 
and Boost have terrible reputation in my industry and are outright 
banned in many companies.

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