D1095R0/N2xxx draft 4: Zero overhead deterministic failure - A unified mechanism for C and C++

318 views
Skip to first unread message

Niall Douglas

unread,
Aug 8, 2018, 3:56:47 PM8/8/18
to ISO C++ Standard - Future Proposals
Please find attached draft 4 of the previous C _Either(A, B) paper, with this edition completely rearchitected to meet WG14 feedback. It is very different from the C _Either proposal sent here earlier.

Changes since draft 3:
  • Dropped section on Rust for brevity.
  • Rebased onto _Fails syntax as per advice from the WG14 reflector.
  • Converted _Either(A, B) from a meta-type into an ordinary sum type as per WG14 reflector suggestion.
  • Added _Fails(errno) to solve the errno impurity problem as suggested by the WG14 reflector.
I'm hoping that this draft strongly resembles the final paper to be submitted for Pittsburgh and San Diego, but we'll see what you make of it.

I'm also going to send this draft to the Austin Working Group for feedback, as I promised WG14 that I would do.

Thanks in advance for any comments.

Niall

D1095-Nxxxx_Zero_overhead_deterministic_failure_draft4.pdf

Niall Douglas

unread,
Aug 10, 2018, 4:34:30 AM8/10/18
to ISO C++ Standard - Future Proposals
Just to keep everybody on WG21 up to date, WG14 likes this proposal a lot, and apart from the .left, .right and .positive being replaced with _EitherValue(), _EitherError() and _EitherHasValue(), the above is essentially the paper to be published.

Obviously if you have any additional feedback, please send it.

Niall

Jake Arkinstall

unread,
Aug 10, 2018, 5:49:26 AM8/10/18
to std-pr...@isocpp.org
The version you posted is the most elegant that I have seen. My only concern was what the C guys would think about the errno discussion, but that was obviously misplaced.

However, I think the new syntax is uglier than the struct approach. Can you provide a code example?

I get why the prefix is wanted, but that prefix is screwing up the connection between name and meaning. If I said to you "either value", it will make you think theres some kind of selection between two values. Same with error and hasvalue. I think this is going to confuse the hell out of fresh eyes.

--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/b1e954fb-bab8-4746-abf1-2e699e702787%40isocpp.org.

Dimitrij Mijoski

unread,
Aug 10, 2018, 6:22:56 AM8/10/18
to ISO C++ Standard - Future Proposals
From C++ point of view this seems to me like an implementation detail for the zero-overhead deterministic exceptions, rather than actual language specification. I'd rather see an actual implementation, than a specification that we don't know if it works. Only when you try to implement something, you will notice all the details you are probably missing now.

From C point of view, you are proposing a new feature, a new way of error handing in C, that is also very similar to the zero-overhead deterministic exceptions. Then you propose some compatibility between your C proposal and the C++ zero-overhead deterministic exception.

The question is, why would you want to bring in a C++ feature into C? Should we maybe try to map C++ classes and polymorphism and templates into C features?

Compatibility comes in two ways, calling C++ code from C, and calling C code from C++. Lets look at them.

  1. Calling C++ code from C is done by creating wrapper C++ functions that hide all the C++ features (only use C types), and then we expose these functions via C compatible header and with the extern "C" stuff. With this proposal this will continue to be this way.
  2. Calling C code from C++ comes (mostly) out of the box. Now lets say this proposal gets accepted into C. To get out of the box support for C code, in C++ _Fails, _Either have to be copied into C++ specs too. Now let's say zero-overhead deterministic exceptions get into C++. Now we have two syntaxes for the same feature. I don't like that. And maybe a third syntax will be needed to create the mappings.

Jake Arkinstall

unread,
Aug 10, 2018, 6:55:12 AM8/10/18
to std-pr...@isocpp.org
On Fri, 10 Aug 2018, 11:22 Dimitrij Mijoski, <dim....@gmail.com> wrote:
From C point of view, you are proposing a new feature, a new way of error handing in C, that is also very similar to the zero-overhead deterministic exceptions. Then you propose some compatibility between your C proposal and the C++ zero-overhead deterministic exception.

The question is, why would you want to bring in a C++ feature into C?

The question is, as far as I'm concerned, Why wouldn't you?

We are both facing a problem with error handling. The C approach doesn't scale, and the C++ approach is inefficient. So it is a known problem in both languages, for different reasons.

If you have to work with some C library in a C++ setting (e.g. sockets), you notice that your code becomes rather messy. Half of your code is just in trying to catch errors in C and convert them into a format suitable for a C++ user (the other half is declaring would-be-temporaries just so that C functions can accept them as pointers). C handling errors in a sane way would easily cut my signalling codebase in half.

We can't have exactly the same syntax, particularly because we have access to nicer syntax and want to keep it that way. We also have different conventions, and I don't want to be adopting C's.

We also have a lot of shared functionality. Our math libraries, for example, are (though implementation defined) identical except for namespacing, and the errno setting in C prevents constexpr math. We could either branch off and make the two languages incompatible, or unify the approach to allow the communities to work together.

A unified approach is easier for both to work with, and makes it easier on C++ programmers who want to interface with C. Low latency developers in particular will love that - and that's why SG14 is involved.

Dimitrij Mijoski

unread,
Aug 10, 2018, 7:12:23 AM8/10/18
to ISO C++ Standard - Future Proposals
Error handing is not the only problem in C. It has a weak type system, so maybe try to bring the C++ type system into C? C memory management is all manual, so maybe bring RAII into C?

Or just stop using C and start using C++. In your example, just start using a C++ socket library in the first place. There are plenty of them.

Bengt Gustafsson

unread,
Aug 10, 2018, 7:12:39 AM8/10/18
to ISO C++ Standard - Future Proposals
Some small comments:

1. The names left and right are not descriptive for their uses. I would prefer expected and unexpected or value and error or something like that.

2. The abs() example has a few flaws.
   a) The value returned when errno is set is 0 for the original code and INT_MIN for the rewritten version which is confusing.
   b) The proposed rewrite from a setting of errno + a return statement to a return _Failure() seems scary as the programmer may insert some other code between the errno setting and the return statement.
   c) The code that shows the call site for the abs() example is not equivalent to the original as the function may set errno to 0, which is taken as no error by the original code but is still not "positive" by the new means.
       Upon further inspection it may be that this feature is just not explained clearly enough. I think your intention is that within a _Fails(errno) marked function errno is a local variable. If it is non-zero when a return statement is hit the transformation occurs.
       This seems to work better than my initial understanding that it is the code pattern of setting errno followed by return that is transformed.
   d) It is unclear to me what happens if errno is already != 0 when abs() is called. To get backwards compatibility it seems logical that the local errno gets set from the threadlocal errno when abs is entered. If this is the case, please show how the expression:
       abs(a) + abs(b) is translated (with the problem being that if abs(a) lazily returns an error then abs(b)'s initing of its errno would use a stale value).

3. In your last C example it is unclear if the code is the user written code or the compiler rewrite, and in the latter case if the user had a errno = 0 statement at all.

Jake Arkinstall

unread,
Aug 10, 2018, 7:46:01 AM8/10/18
to std-pr...@isocpp.org

On Fri, 10 Aug 2018, 12:12 Dimitrij Mijoski, <dim....@gmail.com> wrote:
Or just stop using C and start using C++. In your example, just start using a C++ socket library in the first place. There are plenty of them.

And they wrap the C sockets library provided by the operating system. Sure, the wrappers provide a friendlier interface, but while the error handling approaches remain inconsistent there is still SOMEONE putting in all the effort to handle C with C++. If you're in an environment where latency correlates with financial loss, you are unlikely to use boost::asio (fantastic as a library as it is).

I'd love it if there was a widely used operating system designed specifically for C++-like access to low level functionality, but while we have Unix-like systems and windows, we interface with the OS in C.

(Though I do realise the main gotcha here - those low level C libraries won't be changing any time soon)

inkwizyt...@gmail.com

unread,
Aug 10, 2018, 7:49:27 AM8/10/18
to ISO C++ Standard - Future Proposals

If C++ provide some interface for C, then C could start and end C++ exceptions.  This could be useful when C do not want propagate exception in some cases. Each exception will need its own function that do it. From C++ perspective implementation will be simple: `(*ptr).~T();`

How `std::uncaught_exceptions` will exactly work with value exception? You mentioned overhead of cold cache, but it will be implemented in this or this is thing you want avoid?
Maybe give programmers direct access to this? 99% of code could live with this overhead other probably not.
It would see 3 functions:
std::uncaught_exceptions_inc();
std
::uncaught_exceptions_dec();
std
::uncaught_exceptions_set_abort(+[]{ std::abort(); });

Each value exception need call `inc` in constructor and `dec` in destructor of not moved out object. Or you set `set_abort` (per thread) that will prevent incorrect use of `uncaught_exceptions` in scope guards. With this
allow you to skip `inc` and `dec` in your value exceptions. This could be done locally. Rest of code will correctly use `uncaught_exceptions` and some part where performance is required you prevent use of this.
It could work other way around, you disable this globally but enable it back for some external library calls that use `uncaught_exceptions` internally.

 

Pedro Alves

unread,
Aug 10, 2018, 12:32:09 PM8/10/18
to std-pr...@isocpp.org, Niall Douglas
On 08/10/2018 09:34 AM, Niall Douglas wrote:
> Just to keep everybody on WG21 up to date, WG14 likes this proposal a lot, and apart from the .left, .right and .positive being replaced with _EitherValue(), _EitherError() and _EitherHasValue(), the above is essentially the paper to be published.
>
> Obviously if you have any additional feedback, please send it.

I understand you're aiming at absolute minimum to improve chances
of success, but I wonder whether there is any plan to later on add
an stdbool.h-like header that gives these new symbols prettier
non-_Uppercase (non-implementation-reserved) names, like:

#define fails _Fails
#define try _Try
#define catch _Catch
#define failure _Failure
#define either _Either
...
etc.

?

It feels like if the proposal succeeds, this new failure mechanism
will be used pervasively, and people will quickly get bored with
typing all that _Uppercasing for such a core feature, and
projects will grow such defines themselves.

With that, for mixed C and C++ codebases, and for ease of
codebase migration from C to C++, it'd be ideal if C code using
the prettified "try" and "catch" could compile with a C++
compiler. I'm not sure that would be possible. Seems worth
it to me to aim at making this path down the road possible.
I was a bit surprised to not see this discussed in the paper.

Your example in the paper would read instead:

#include <stdwhatever.h>

int some_function(int x) fails(float)
{
return (x != 0) ? 5 : failure(2.0);
}

const char *some_other_function(int x) fails(float)
{
int v = try(some_function(x));
return (v == 5) ? "Yes" : "No";
}

int main(int argc, char *argv[])
{
if(argc < 2)
abort();
either(const char *, float) v = catch(some_other_function(atoi(argv[1])));
if(v.positive)
{
printf("v is a successful %s\n", v.left);
}
else
{
printf("v is failure %f\n", v.right);
}
return 0;
}

Thanks,
Pedro Alves

Niall Douglas

unread,
Aug 12, 2018, 2:50:44 PM8/12/18
to ISO C++ Standard - Future Proposals
On Friday, August 10, 2018 at 11:22:56 AM UTC+1, Dimitrij Mijoski wrote:
From C++ point of view this seems to me like an implementation detail for the zero-overhead deterministic exceptions, rather than actual language specification. I'd rather see an actual implementation, than a specification that we don't know if it works. Only when you try to implement something, you will notice all the details you are probably missing now.

A major compiler vendor intends to implement P0709 by early 2019.

The papers forming this discussion is to set the general approach any prototype should take.

Regarding better type interop between C and C++, well baby steps first. Let's solve the exception throwing interop problem, and move on from there. I would also add that there are languages which only speak C, but have much better interop with C++'s richer type system.

Niall

Niall Douglas

unread,
Aug 12, 2018, 6:12:40 PM8/12/18
to ISO C++ Standard - Future Proposals

1. The names left and right are not descriptive for their uses. I would prefer expected and unexpected or value and error or something like that.

Already changed to _EitherValue() and _EitherError().
 
   b) The proposed rewrite from a setting of errno + a return statement to a return _Failure() seems scary as the programmer may insert some other code between the errno setting and the return statement.

Optimisation already significantly rewrites the programmer's code. Everything is "as if" nowadays.
 
   c) The code that shows the call site for the abs() example is not equivalent to the original as the function may set errno to 0, which is taken as no error by the original code but is still not "positive" by the new means.
       Upon further inspection it may be that this feature is just not explained clearly enough. I think your intention is that within a _Fails(errno) marked function errno is a local variable. If it is non-zero when a return statement is hit the transformation occurs.
       This seems to work better than my initial understanding that it is the code pattern of setting errno followed by return that is transformed.

Correct, real errno becomes exclusively the compiler's responsibility when calling a _FailsWithErrno function.
 
   d) It is unclear to me what happens if errno is already != 0 when abs() is called. To get backwards compatibility it seems logical that the local errno gets set from the threadlocal errno when abs is entered. If this is the case, please show how the expression:
       abs(a) + abs(b) is translated (with the problem being that if abs(a) lazily returns an error then abs(b)'s initing of its errno would use a stale value).

If you declare your function _FailsWithErrno, it needs to be ported to the new system. Specifically, that you can't read errno. Trying to do so fails the compile.

For code which calls a _FailsWithErrno function, we know such functions never read errno, but they as-if may set it. The compiler optimises accordingly.
 

3. In your last C example it is unclear if the code is the user written code or the compiler rewrite, and in the latter case if the user had a errno = 0 statement at all.

I will clarify the wording and spell out why and how statements were elided by the compiler due to having no possible side effect.

Niall 

Niall Douglas

unread,
Aug 12, 2018, 6:19:16 PM8/12/18
to ISO C++ Standard - Future Proposals

How `std::uncaught_exceptions` will exactly work with value exception? You mentioned overhead of cold cache, but it will be implemented in this or this is thing you want avoid?

Me personally, I'd like std::uncaught_exceptions to apply to type-based exception throws only, and not drag down all value-based exception throws with latency just for the 0.01% of users which ever use std::uncaught_exceptions. It could maybe be enabled with a compiler option.

But assuming WG21 won't go for that, my proposal is that the TLS reference counting only apply to std::error, it is not available under Freestanding C++, and can be disabled at the command line.

Niall

Niall Douglas

unread,
Aug 12, 2018, 6:20:52 PM8/12/18
to ISO C++ Standard - Future Proposals, nialldo...@gmail.com, pe...@palves.net
I understand you're aiming at absolute minimum to improve chances
of success, but I wonder whether there is any plan to later on add
an stdbool.h-like header that gives these new symbols prettier
non-_Uppercase (non-implementation-reserved) names, like:

WG14 already has bikeshedded on such a suite of said macros.

So tldr yes that's exactly what would happen, if this proposal gets any steam. Nobody would be writing underscoreCapital anything.

Niall 
Reply all
Reply to author
Forward
0 new messages