Status of Expected proposal (Re: [std-proposals] Re: Parsing Numbers)

569 views
Skip to first unread message

Vicente J. Botet Escriba

unread,
May 20, 2015, 11:09:01 AM5/20/15
to std-pr...@isocpp.org
Le 19/05/15 03:01, Nicol Bolas a écrit :
On Monday, May 18, 2015 at 6:30:38 PM UTC-4, Jeffrey Yasskin wrote:
On Mon, May 18, 2015 at 2:57 PM, Nicol Bolas <jmck...@gmail.com> wrote:
> I just want to see the "sto*" functions get string_view ASAP. We shouldn't
> wait on `expected` just to accomplish that.

That seems like a straightforward paper to get through the committee.
If you write it, I'll try to prevent the group from creeping its
scope.

Actually, that paper already exists (N4015), with a revision (N4109). According to Vicente Escriba, who both wrote those proposals and replied here, scope creep has already started. Also, N4109 has been a while ago (almost a year now), with no followup paper from any discussions based on it.

Yes its me. There is an error on the web page that I have already reported. My name is Vicente J. Botet Escriba. Vicente J. is my fisrt name and Botet Escriba is my last name.

The expected proposal was blocked as the expected proposal was proposing an alternative way to report errors and the standard C++ has already one: exceptions. A study of all the alternative ways to reporting errors was requested so we can take a decision on which ones could be used once the advantages and liabilities of each approach are detailed. I'm not a paper writer and writing such a paper needs to be done carefully and it would takes time. If there are any volunteers to write it, please do it, it will be a pleasure to help. Of course, I will be very happy is this paper unblocks the expected proposal.
I think at this point, we should focus on getting the core feature: parsing strings via string_view. And those many malign the FileSystem TS solution, it is prior art on dealing with error codes in standard library C++.

I don't share the FileSystem design to report errors. I have used it in Boost.Chrono, and it really doesn't scale. It is difficult to say it is prior art when we include it in the FS TS. For me, it is the temporary C++ standard way of defining interfaces that report errors without using exceptions until we have a better way.

Vicente

Tony V E

unread,
May 20, 2015, 2:05:31 PM5/20/15
to Standard Proposals
Yes I really think we should use FS as an example of "there must be a better way to do this".  To me that is either:

- just use exceptions
or
- something like expected<>

The FS should have half as many functions as it currently has.  I hope that is fixed before standardization.

Tony

 

--

---
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.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.

Nicol Bolas

unread,
May 20, 2015, 2:09:20 PM5/20/15
to std-pr...@isocpp.org
On Wednesday, May 20, 2015 at 2:05:31 PM UTC-4, Tony V E wrote:
On Wed, May 20, 2015 at 11:08 AM, Vicente J. Botet Escriba <vicent...@wanadoo.fr> wrote:
Le 19/05/15 03:01, Nicol Bolas a écrit :
On Monday, May 18, 2015 at 6:30:38 PM UTC-4, Jeffrey Yasskin wrote:
On Mon, May 18, 2015 at 2:57 PM, Nicol Bolas <jmck...@gmail.com> wrote:
> I just want to see the "sto*" functions get string_view ASAP. We shouldn't
> wait on `expected` just to accomplish that.

That seems like a straightforward paper to get through the committee.
If you write it, I'll try to prevent the group from creeping its
scope.

Actually, that paper already exists (N4015), with a revision (N4109). According to Vicente Escriba, who both wrote those proposals and replied here, scope creep has already started. Also, N4109 has been a while ago (almost a year now), with no followup paper from any discussions based on it.

Yes its me. There is an error on the web page that I have already reported. My name is Vicente J. Botet Escriba. Vicente J. is my fisrt name and Botet Escriba is my last name.

The expected proposal was blocked as the expected proposal was proposing an alternative way to report errors and the standard C++ has already one: exceptions. A study of all the alternative ways to reporting errors was requested so we can take a decision on which ones could be used once the advantages and liabilities of each approach are detailed. I'm not a paper writer and writing such a paper needs to be done carefully and it would takes time. If there are any volunteers to write it, please do it, it will be a pleasure to help. Of course, I will be very happy is this paper unblocks the expected proposal.
I think at this point, we should focus on getting the core feature: parsing strings via string_view. And those many malign the FileSystem TS solution, it is prior art on dealing with error codes in standard library C++.

I don't share the FileSystem design to report errors. I have used it in Boost.Chrono, and it really doesn't scale. It is difficult to say it is prior art when we include it in the FS TS. For me, it is the temporary C++ standard way of defining interfaces that report errors without using exceptions until we have a better way.

Vicente


Yes I really think we should use FS as an example of "there must be a better way to do this".  To me that is either:

- just use exceptions
or
- something like expected<>

The FS should have half as many functions as it currently has.  I hope that is fixed before standardization.

I personally have no complaints about the filesystem's method of doing error codes. But I also wouldn't be adverse to seeing `expected` used instead of error code parameter outputs.

But one thing I'm adamantly against is being forced to use error codes. They should be optional, not mandatory. In C++, exceptions are the standard way of signaling errors; I shouldn't have to deal with error codes unless I choose to.

So I would say that filesystem should keep the same number of functions. Either that, or it shouldn't support error codes at all.

Thiago Macieira

unread,
May 20, 2015, 2:29:47 PM5/20/15
to std-pr...@isocpp.org
On Wednesday 20 May 2015 11:09:20 Nicol Bolas wrote:
> But one thing I'm adamantly against is being forced to use error codes.
> They should be *optional*, not mandatory. In C++, exceptions are the
> standard way of signaling errors; I shouldn't have to deal with error codes
> unless I choose to.

True, and on the other hand I'm hoping that the committee realises that
there's a world without exceptions. There are many environments where
exceptions cannot be used and it's irrelevant whether those reasons are good
or bad. Making APIs that work for those environments increases their use-base.

--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center
PGP/GPG: 0x6EF45358; fingerprint:
E067 918B B660 DBD1 105C 966C 33F5 F005 6EF4 5358

Matthew Fioravante

unread,
May 20, 2015, 2:49:59 PM5/20/15
to std-pr...@isocpp.org
On Wednesday, May 20, 2015 at 11:09:01 AM UTC-4, Vicente J. Botet Escriba wrote:
Yes its me. There is an error on the web page that I have already reported. My name is Vicente J. Botet Escriba. Vicente J. is my fisrt name and Botet Escriba is my last name.

The expected proposal was blocked as the expected proposal was proposing an alternative way to report errors and the standard C++ has already one: exceptions. A study of all the alternative ways to reporting errors was requested so we can take a decision on which ones could be used once the advantages and liabilities of each approach are detailed. I'm not a paper writer and writing such a paper needs to be done carefully and it would takes time. If there are any volunteers to write it, please do it, it will be a pleasure to help. Of course, I will be very happy is this paper unblocks the expected proposal.

The string_view to int paper could be a good candidate for such a study.

On Wednesday, May 20, 2015 at 2:29:47 PM UTC-4, Thiago Macieira wrote:
On Wednesday 20 May 2015 11:09:20 Nicol Bolas wrote:
> But one thing I'm adamantly against is being forced to use error codes.
> They should be *optional*, not mandatory. In C++, exceptions are the
> standard way of signaling errors; I shouldn't have to deal with error codes
> unless I choose to.

True, and on the other hand I'm hoping that the committee realises that
there's a world without exceptions. There are many environments where
exceptions cannot be used and it's irrelevant whether those reasons are good
or bad. Making APIs that work for those environments increases their use-base.

I agree with Thiago.

Unfortunately, exceptions cannot be the standard C++ way. There needs to be an exception way and a non-exception way. Maybe its kind of ugly to have to support both but that is the reality.

The big problem with exceptions is that if you do not carefully handle every possible exception an interface can throw your application will crash. Even worse, many of these exceptions only occur for exceptional scenarios which are very hard and expensive (in programmer time) to fully cover in unit tests. If you turn on exceptions, you can never be quite sure there isn't some edge case out there that will throw and crash your whole program.

A good example of this is the cppformat library which is a modern printf replacement. This library throws exceptions if you don't match the number of parameters with the number of "{}" in your format string. The first thing I did was disable exceptions here. Imagine your entire system crashing just because someone forgot to format a log message correctly? I've been in plenty of environments where a crash at the wrong time in production could result in huge unrecoverable losses.

I like expected because it pushes the question of exception handling onto the client without needing duplicate exception and non-exception interfaces. If the client checks the error status of expected his application will never throw and the optimizer can even remove all of the throwing code and consider the function body functionally noexcept.

I imagine implementations could even produce a warning such that when -fnoexceptions is enabled it will warn whenever an expected object value is referenced without checking the error condition first. That would go a long way in helping programmers handle errors.
 

Nicol Bolas

unread,
May 20, 2015, 3:02:03 PM5/20/15
to std-pr...@isocpp.org


On Wednesday, May 20, 2015 at 2:49:59 PM UTC-4, Matthew Fioravante wrote:
On Wednesday, May 20, 2015 at 11:09:01 AM UTC-4, Vicente J. Botet Escriba wrote:
Yes its me. There is an error on the web page that I have already reported. My name is Vicente J. Botet Escriba. Vicente J. is my fisrt name and Botet Escriba is my last name.

The expected proposal was blocked as the expected proposal was proposing an alternative way to report errors and the standard C++ has already one: exceptions. A study of all the alternative ways to reporting errors was requested so we can take a decision on which ones could be used once the advantages and liabilities of each approach are detailed. I'm not a paper writer and writing such a paper needs to be done carefully and it would takes time. If there are any volunteers to write it, please do it, it will be a pleasure to help. Of course, I will be very happy is this paper unblocks the expected proposal.

The string_view to int paper could be a good candidate for such a study.

On Wednesday, May 20, 2015 at 2:29:47 PM UTC-4, Thiago Macieira wrote:
On Wednesday 20 May 2015 11:09:20 Nicol Bolas wrote:
> But one thing I'm adamantly against is being forced to use error codes.
> They should be *optional*, not mandatory. In C++, exceptions are the
> standard way of signaling errors; I shouldn't have to deal with error codes
> unless I choose to.

True, and on the other hand I'm hoping that the committee realises that
there's a world without exceptions. There are many environments where
exceptions cannot be used and it's irrelevant whether those reasons are good
or bad. Making APIs that work for those environments increases their use-base.

I agree with Thiago.

Unfortunately, exceptions cannot be the standard C++ way.

But they are the standard way. That's how the standard signals failure. Indeed, C++ class construction cannot work without exceptions (or at least, cannot fail to work. Not in anything remotely like a sane manner). And pretty much everything in the standard library that wasn't directly inherited from C uses exceptions.

It is the standard way. The question is whether we should also, in some cases, have an alternative. And what that alternative should be.

Nicol Bolas

unread,
May 20, 2015, 3:21:23 PM5/20/15
to std-pr...@isocpp.org
On Wednesday, May 20, 2015 at 2:49:59 PM UTC-4, Matthew Fioravante wrote:
A good example of this is the cppformat library which is a modern printf replacement. This library throws exceptions if you don't match the number of parameters with the number of "{}" in your format string. The first thing I did was disable exceptions here. Imagine your entire system crashing just because someone forgot to format a log message correctly?

This is actually a perfect example of why you shouldn't turn off exceptions like this. As well as illustrating the limitations of `expected`. Why?

Because by turning off exceptions, you allow bugs to exist. If the user passes the wrong parameters to the logging function, that is a bug. And because you turned off exceptions, you won't know its there unless you check your log. And even then, you might simply miss it (it's not clear what happens in the event of such an error). Not to mention, there's the question of how `cppformat` behaves when you turn off exception handling; does it do something sane, like not emit a string, or does it start walking and/or trashing random memory?

`expected` can't fix that, since there's no return value from logging. So not only is there an error, but you have no means of communicating that error. Oh, you could return an `expected<void, E>`, but who checks a return value from a function that doesn't have one?

It should also be noted that the exception behavior is much better than, say, printf's behavior when given the wrong set of parameters. That behavior being undefined, which will almost certainly involve walking and/or trashing random memory. A guaranteed exception/crash is better than a "maybe crash, maybe overwrite the balance on someone's account."

I've been in plenty of environments where a crash at the wrong time in production could result in huge unrecoverable losses.

Arbitrarily trashing memory at the wrong time is just as bad, if not worse.

Exceptions aren't bad; generating errors is what is bad. Exceptions are just telling you that you generated one and forcing you to actually deal with it, rather than pretending it didn't happen.

It should also be noted that, if you enter a scenario where crashing via exception is not an option, you can always bracket it in a catch(...) clause. In that case, it's basically about cleanup for something fantastically bad that happened; just restore things to a sane state and proceed.

Matthew Fioravante

unread,
May 20, 2015, 3:55:53 PM5/20/15
to std-pr...@isocpp.org


On Wednesday, May 20, 2015 at 3:21:23 PM UTC-4, Nicol Bolas wrote:
On Wednesday, May 20, 2015 at 2:49:59 PM UTC-4, Matthew Fioravante wrote:
A good example of this is the cppformat library which is a modern printf replacement. This library throws exceptions if you don't match the number of parameters with the number of "{}" in your format string. The first thing I did was disable exceptions here. Imagine your entire system crashing just because someone forgot to format a log message correctly?

This is actually a perfect example of why you shouldn't turn off exceptions like this. As well as illustrating the limitations of `expected`. Why?

Because by turning off exceptions, you allow bugs to exist.

I can live with an incorrect line in a log file (and an additional message in the log file to tell me there was an error with cppformat that I need to fix). I cannot live with a crash in production for something benign which could result in more losses for my organization than my yearly income.
 
If the user passes the wrong parameters to the logging function, that is a bug. And because you turned off exceptions, you won't know its there unless you check your log.

You can always reenable exceptions or assert() in debug builds to catch these errors. In general I am a devout follower of the "fail early and loudly" approach but this isn't always appropriate for all production environmets.
 
And even then, you might simply miss it (it's not clear what happens in the event of such an error). Not to mention, there's the question of how `cppformat` behaves when you turn off exception handling; does it do something sane, like not emit a string, or does it start walking and/or trashing random memory?

This particular library has support for enabling/disabling exceptions via a macro. Of course blindly disabling exceptions when you aren't sure if the library will work correctly without them is not a real solution.
 

`expected` can't fix that, since there's no return value from logging. So not only is there an error, but you have no means of communicating that error. Oh, you could return an `expected<void, E>`, but who checks a return value from a function that doesn't have one?

Well of course in this example the best scenario is to have the compiler check the format string. expected doesn't help with my example but its an example to show when exceptions can do more harm then good, not a use case for expected.
 

It should also be noted that the exception behavior is much better than, say, printf's behavior when given the wrong set of parameters. That behavior being undefined, which will almost certainly involve walking and/or trashing random memory. A guaranteed exception/crash is better than a "maybe crash, maybe overwrite the balance on someone's account."

I agree, an assert() or throw or crash is always better than UB. The situation however is not always this extreme.
 

I've been in plenty of environments where a crash at the wrong time in production could result in huge unrecoverable losses.

Arbitrarily trashing memory at the wrong time is just as bad, if not worse.

Its not always a black and white choice of throw an exception or trigger UB. Like the example of my logger, its can be a choice of throw an exception and crash or output some garbage to the log file.
 

Exceptions aren't bad; generating errors is what is bad. Exceptions are just telling you that you generated one and forcing you to actually deal with it, rather than pretending it didn't happen.

Not all errors are the same.

Some are critical, in which an assert()/throw/abort() is appropriate. Some are slightly annoying but mostly benign (logging). Some will reduce the performance of your objective (say 1 module out of 10 fails, so now we only run at 90% capacity) but are not critical enough to bring the whole system down and run at 0% capacity.
 

It should also be noted that, if you enter a scenario where crashing via exception is not an option, you can always bracket it in a catch(...) clause. In that case, it's basically about cleanup for something fantastically bad that happened; just restore things to a sane state and proceed.

This approach works sometimes when you can firewall exceptions around a small well defined section of code. For example if you have a routine which parses a file and returns an object, you can use an IO library which throws exceptions and wrap the usage in a try/catch. You can easily log the IO error in the catch() clause and then return your "error" status outside to the exception unsafe world.

In general however, this approach sounds nice in theory but not so easy in practice. Your "restore things to a sane state and proceed" stage requires a lot of care. In order to recover from an error you need to know which error occurred, what triggered it, and how to rollback the entire application to a valid state.. Unless you're carefully using RAII to rollback every action, you need to be extremely careful. My IO example this approach was doable because it has simple "All or nothing" semantics.

I can't hope to recover anything if I just catch a random exception from god knows where. Blindly trying to resume without knowing what actually happened or what the side effects are sounds like a recipe for intractable bugs. You cannot possibly hope to unit test or make any statements about correctness for all of those code paths.

In the same way that using new / delete (instead of unique_ptr) means you can never be quite sure your program doesn't leak, using exceptions means you can never be quite sure it won't randomly crash.


Nevin Liber

unread,
May 20, 2015, 4:34:59 PM5/20/15
to std-pr...@isocpp.org
On 20 May 2015 at 14:55, Matthew Fioravante <fmatth...@gmail.com> wrote:

I can live with an incorrect line in a log file (and an additional message in the log file to tell me there was an error with cppformat that I need to fix).

I'm not sure why you'd ever get that second line.  Either it is a precondition for calling your function or it isn't.
 
I cannot live with a crash in production for something benign which could result in more losses for my organization than my yearly income.

How do you know the programming bug is benign and not symptomatic of something far worse?

You are pretending the code knows the root cause of the programming bug it was never supposed to have and can magically recover from it.  Seems very risky to me...
--
 Nevin ":-)" Liber  <mailto:ne...@eviloverlord.com(847) 691-1404

Matt Calabrese

unread,
May 20, 2015, 4:52:39 PM5/20/15
to std-pr...@isocpp.org
On Wed, May 20, 2015 at 12:21 PM, Nicol Bolas <jmck...@gmail.com> wrote:
On Wednesday, May 20, 2015 at 2:49:59 PM UTC-4, Matthew Fioravante wrote:
A good example of this is the cppformat library which is a modern printf replacement. This library throws exceptions if you don't match the number of parameters with the number of "{}" in your format string. The first thing I did was disable exceptions here. Imagine your entire system crashing just because someone forgot to format a log message correctly?

This is actually a perfect example of why you shouldn't turn off exceptions like this. As well as illustrating the limitations of `expected`. Why?

Because by turning off exceptions, you allow bugs to exist. If the user passes the wrong parameters to the logging function, that is a bug. And because you turned off exceptions, you won't know its there unless you check your log. And even then, you might simply miss it (it's not clear what happens in the event of such an error). Not to mention, there's the question of how `cppformat` behaves when you turn off exception handling; does it do something sane, like not emit a string, or does it start walking and/or trashing random memory?

`expected` can't fix that, since there's no return value from logging. So not only is there an error, but you have no means of communicating that error. Oh, you could return an `expected<void, E>`, but who checks a return value from a function that doesn't have one?

It should also be noted that the exception behavior is much better than, say, printf's behavior when given the wrong set of parameters. That behavior being undefined, which will almost certainly involve walking and/or trashing random memory. A guaranteed exception/crash is better than a "maybe crash, maybe overwrite the balance on someone's account."

At risk of derailing this discussion, I want to take a step back and give my perspective about exceptions in current C++, "expected", and what I personally consider "ideal" for exceptional cases (failures to meet a post-condition).

"expected", or discriminated unions in general, provide many benefits, and some drawbacks, over exceptions:

==========

Discriminated union (treated as "expected" with N unexpected cases):
Pros:
Not dependent on some global RTTI
Control flow is very clear
Handling of errors is generally as efficient as dealing with results
Users know exactly the kinds of errors that can come up and if they are all handled
Can effectively turn the closed-set into an open set by having an "expected" with a runtime polymorphic unexpected type
Cons:
Propagation is not automatic
Deals with a closed-set of types (also a pro, this is a trade-off)
You pay for the exceptional case by way of the return value and branching even though it is an "uncommon" case
Errors can be silently missed if the return value is not checked (common if return is void or there are other side-effects)
Changing the set of error kinds can change the function type
Dealing with the result of a function that can fail vs one that cannot is considerably different
Not directly applicable for constructors (generally implies "make" facilities that yield expected and is considerably annoying for copy operations that can fail)
(IMO) Difficult to work with in generic code that needs to potentially propagate errors from user-defined code that may or may not throw

C++ Exceptions:
Pros:
Open set of types (this is a trade-off)
Function type does not change as different kinds of failures are added/removed
Dealing with the expected return type is as easy as the case when the function cannot fail
Changing a function that is noexcept to one that that is not (or vice versa) does not affect use of the result
They play well with constructors
(IMO) Easier to work with in generic code that calls into user-defined functions that may or may not throw and an exception would need to propagate
Cons:
Open set of exception types means it's difficult to know if all exception kinds are dealt with
Control flow can be extremely subtle and complex when taking into account exceptions, especially in generic code that interacts with user-defined functions that may or may not throw
Open set of types and the way catching works means dependence on more sophisticated RTTI

==========

Feel free to correct anything that is wrong in the above assessment or to add to it. I am not an expert in this domain and these are just my personal thoughts.

First off, the most obvious thing to get out of this, in my opinion, is that "we already have exceptions" is not a sufficient answer for why we should avoid standardizing something like "expected". There are trade-offs and the assumption that using C++ exceptions is always the proper solution is as flawed as saying that everyone should always use specifically any one of the standard containers. Different situations have different needs. I really do feel that something like "expected" is worthwhile, even though I find that the proposal has some separate flaws.

Not that the following has any chance of changing the language, but my personal thoughts have been that C++ exceptions are good, but would ideally be much more like a discriminated union in terms of implementation. By that I mean they still would not affect the return type of the function, as is already the case with current exceptions though not with "expected", but they would deal with a closed set of types rather than an open set. You would be able to query at compile time the set of possible exceptions and append to that set when making higher level functions (important for generic code -- this is also analogous to using noexcept to determine if an expression can throw or not). You'd always be able to effectively make an open set of exception types by using some kind of a polymorphic type (I.E. a type-erased object with a large small-object optimization), though you would lose some of the functionality of C++ exceptions as they are in the language. Exception propagation would continue to work as-is, as would accessing of the normal result of a function. Constructors would also work as easily as they do right now.

This would basically just be C++ exceptions, only implemented on top of a discriminated union instead of an open set of types.

Anyway, some of this is sort of a derail but those are my thoughts. In practice I find that using something like "expected" is very useful and has important practical implications, but I'm also a fan of many of the properties of exceptions.

Jeremy Maitin-Shepard

unread,
May 20, 2015, 5:33:55 PM5/20/15
to std-pr...@isocpp.org
The version of excepted that holds an exception_ptr is also very useful even when exceptions are used: it encapsulates the "result or exception" pattern that underlies std::future and is very useful in cases where the heavy-weight notification and waiting functionality (as well as thread safety) of std::future is not required.  It would probably make sense to make std::future explicitly interoperable with excepted.

Nevin Liber

unread,
May 20, 2015, 6:23:43 PM5/20/15
to std-pr...@isocpp.org
On 20 May 2015 at 13:09, Nicol Bolas <jmck...@gmail.com> wrote:
But one thing I'm adamantly against is being forced to use error codes. They should be optional, not mandatory. In C++, exceptions are the standard way of signaling errors; I shouldn't have to deal with error codes unless I choose to.

But that is not strictly true.

For instance, set::insert(value_type const&) returns a pair whose second element tells you whether or not the insertion succeeded.

It's more about how unusual the failure is, whether it is likely to be handled locally, etc.

For libraries like filesystem, there is no way to know whether the user needs to handle it locally or not, so both are provided.  For instance, on an embedded system, I might expect many of the files I open to just be there, and it would be unusual if they weren't.  OTOH, if the user is typing in a filename somewhere, I probably want to handle that locally.

Nicol Bolas

unread,
May 20, 2015, 6:35:51 PM5/20/15
to std-pr...@isocpp.org
On Wednesday, May 20, 2015 at 6:23:43 PM UTC-4, Nevin ":-)" Liber wrote:
On 20 May 2015 at 13:09, Nicol Bolas <jmck...@gmail.com> wrote:
But one thing I'm adamantly against is being forced to use error codes. They should be optional, not mandatory. In C++, exceptions are the standard way of signaling errors; I shouldn't have to deal with error codes unless I choose to.

But that is not strictly true.

For instance, set::insert(value_type const&) returns a pair whose second element tells you whether or not the insertion succeeded. 

It's more about how unusual the failure is, whether it is likely to be handled locally, etc.

I wouldn't say that.

set::insert returns the second value purely as a convenience to the user. Attempting to insert the same element multiple times is not an error. The purpose of a set is to hold a sequence of unique values. So if you try to insert the same element multiple times... it's no different to the set than if you inserted it once.

In short, it doesn't represent an error. You asked for the item to be put in the list. And after the function call, the item is in the list; you can fetch the item through `set`'s interface, just as if it weren't there before.

The only reason the API even lets the user know the element was actually inserted is because the user might choose to care, in some isolated cases.

Filesystem errors are legitimate error conditions. You asked the system to do something, and it couldn't be done. That's not something you should be able to ignore. Or at least, not without using some specific syntax saying that you're ignoring it.

Matt Calabrese

unread,
May 20, 2015, 6:42:13 PM5/20/15
to std-pr...@isocpp.org
On Wed, May 20, 2015 at 3:23 PM, Nevin Liber <ne...@eviloverlord.com> wrote:
On 20 May 2015 at 13:09, Nicol Bolas <jmck...@gmail.com> wrote:
But one thing I'm adamantly against is being forced to use error codes. They should be optional, not mandatory. In C++, exceptions are the standard way of signaling errors; I shouldn't have to deal with error codes unless I choose to.

But that is not strictly true.

For instance, set::insert(value_type const&) returns a pair whose second element tells you whether or not the insertion succeeded.

It's more about how unusual the failure is, whether it is likely to be handled locally, etc.

I've never been a fan of classification based on how "usual" or "unusual" something is because that often depends more on usage rather than being known at the declaration, and even then what is "usual" vs "unusual" isn't a clear line.

The way I think about things:
Exceptions are for when an implementation cannot meet a post-condition, but where no invariants are violated. Exceptions do not logically change the output space of the function.
Functions with "errors codes" or that use "expected" actually weaken post-conditions by instead expanding the output space of the function such that it now includes those failure states.

The choice of which is used mostly depends on practical concerns (i.e. what ends up being more efficient for an application), and what users consider easier to reason about or to deal with in higher-level code. Regarding what is easier to reason about and deal with, I personally find that strict post conditions (exceptions) are easier to deal with, since the output space is generally smaller. Because of this, in the cases where failures cannot be properly handled at a given point in the call-stack, much of the user's code appears as simple as if the functions couldn't fail at all.

Matthew Woehlke

unread,
May 20, 2015, 7:30:06 PM5/20/15
to std-pr...@isocpp.org
On 2015-05-20 11:08, Vicente J. Botet Escriba wrote:
> The expected proposal was blocked as the expected proposal was proposing
> an alternative way to report errors and the standard C++ has already
> one: exceptions.

I'd have to reiterate the position taken by others here... exceptions
are horrible. Plenty of code pretends that exceptions don't exist and/or
is built with exceptions disabled. The cognative overhead of catching
exceptions vs. just checking something like a std::expected is high. The
performance cost of [error handling using] exceptions vs. something like
std::expected is high.

The only mechanisms I'm aware of are:

- exceptions (see above)
- C errno (ugh)
- out parameters to receive error/success
- std::optional
- std::expected

Out parameters are inelegant, and IIRC said inelegance was a direct
contribution for the desire for expected. std::optional is closer, but
has no way of communicating *what* went wrong. It's also no accident
that std::expected is something of an extension to std::optional.

IMO the use for std::expected is clear. (Alas, I am not much help
writing a paper.)

--
Matthew

Matthew Woehlke

unread,
May 20, 2015, 7:35:09 PM5/20/15
to std-pr...@isocpp.org
On 2015-05-20 14:09, Nicol Bolas wrote:
> But one thing I'm adamantly against is being forced to use error codes.
> They should be *optional*, not mandatory. In C++, exceptions are the
> standard way of signaling errors; I shouldn't have to deal with error codes
> unless I choose to.

IMNSHO it's *exceptions* that should be optional, not mandatory. I write
C++ for a living *and* as a hobby, and in both cases, rarely if ever use
or deal with exceptions. I'm talking about cases where either I have
turned them off in the compiler and/or have code that would abort if an
exception gets thrown.

Anyway, that's what's great about std::expected... if you just assume it
is always valid, it throws when you try to access it... so (if you want
to use it that way) the only difference between returning a
std::expected and having the function throw in the first place is where
the exception is thrown. In many cases, this change of location is not
even relevant to how you write your code, especially if you just take
the value of the std::expected as soon as it is returned.

Conversely, if exceptions aren't for you, you can treat std::expected
like a pointer - always check it before "dereferencing" and accept that
your program will abort or do some other horrible thing if you fail to
do so and it was invalid - and not have to worry about "those
'exception' things".

(@Matthew F., warning about an unchecked std::exxpected might be a
little much; the programmer might know that an error can't occur, or
might be happy having the program crash if one does... same as we don't
have warnings about possibly dereferencing null pointers. For that
matter, getting such a warning right - i.e. not having false positives -
is infernally hard.)

--
Matthew

Matthew Woehlke

unread,
May 20, 2015, 7:42:18 PM5/20/15
to std-pr...@isocpp.org
On 2015-05-20 16:52, 'Matt Calabrese' via ISO C++ Standard - Future
Proposals wrote:
> Discriminated union (treated as "expected" with N unexpected cases):
> Cons:
> Errors can be silently missed if the return value is not checked (common if
> return is void or there are other side-effects)

I'd like to point out here that this doesn't really apply. If you don't
return a meaningful result, but might return an error, and you *need* to
enforce that the caller checks if an error occurred, then the proper way
to do that is *not* std::expected or something like it, but rather a
class that may-or-may-not contain an error, that keeps an internal state
noting if it has been checked or not, and throws on destruction if it
has not been checked. I've worked with these before, and while it might
be nice to have something to this effect in the STL, I consider it an
orthogonal issue to the cases where std::expected would be used.

(And if you don't need to enforce error checking, then you should be
returning the error directly.)

Of course, having said that, I could imagine something like:

// Return number of bytes read
std::checked_expected<size_t, io_error>
istream::read(char* buffer, size_t max_len);

...where the value itself may or may not be interesting, but it is
desirable to ensure that the result is at least checked for errors.

--
Matthew

Matt Calabrese

unread,
May 20, 2015, 7:56:48 PM5/20/15
to std-pr...@isocpp.org
On Wed, May 20, 2015 at 4:42 PM, Matthew Woehlke <mw_t...@users.sourceforge.net> wrote:
On 2015-05-20 16:52, 'Matt Calabrese' via ISO C++ Standard - Future
Proposals wrote:
> Discriminated union (treated as "expected" with N unexpected cases):
> Cons:
> Errors can be silently missed if the return value is not checked (common if
> return is void or there are other side-effects)

I'd like to point out here that this doesn't really apply. If you don't
return a meaningful result, but might return an error, and you *need* to
enforce that the caller checks if an error occurred, then the proper way
to do that is *not* std::expected or something like it, but rather a
class that may-or-may-not contain an error, that keeps an internal state
noting if it has been checked or not, and throws on destruction if it
has not been checked.

I'm not sure I agree. I'm making a comparison of pure use of expected/discriminated unions to use of exceptions for a particular part of code -- not necessarily in conjunction with exceptions. Throwing doesn't really solve the problem, since very often the reason people use "expected" is either because they can't realistically use exceptions in their application or because they want control flow to be more explicit. Reintroducing exceptions is likely not what many of the users want.

Thiago Macieira

unread,
May 20, 2015, 7:58:07 PM5/20/15
to std-pr...@isocpp.org
On Wednesday 20 May 2015 12:02:03 Nicol Bolas wrote:
> It is the standard way. The question is whether we should also, in some
> cases, have an alternative. And what that alternative should be.

Sorry, I didn't mean to start a discussion about the merits of exceptions
again.

I'm just going to say that I agree with Nicol's paragraph above. The C++
standard has made a choice for exceptions, that's fine. All I'm asking is that
there should be some alternative in some cases.

Nicol Bolas

unread,
May 20, 2015, 8:22:28 PM5/20/15
to std-pr...@isocpp.org, mw_t...@users.sourceforge.net


On Wednesday, May 20, 2015 at 7:30:06 PM UTC-4, Matthew Woehlke wrote:
On 2015-05-20 11:08, Vicente J. Botet Escriba wrote:
> The expected proposal was blocked as the expected proposal was proposing
> an alternative way to report errors and the standard C++ has already
> one: exceptions.

I'd have to reiterate the position taken by others here... exceptions
are horrible. Plenty of code pretends that exceptions don't exist and/or
is built with exceptions disabled.

Plenty of code pretends that error code return values don't exist either. Indeed, that's far more pernicious, because while code can pretend to ignore exceptions, they will still halt the program. While a dropped error code will continue merrily along, with the state of the program becoming increasingly invalid.

The cognative overhead of catching
exceptions vs. just checking something like a std::expected is high.

... how, exactly?
 
The
performance cost of [error handling using] exceptions vs. something like
std::expected is high.

... is it? Are you talking about in the case where an error actually happens, or the case where the error doesn't happen? How frequently does the error condition take place?

You can't possibly talk about the performance of something without cover the actual circumstances of the specific instance. It doesn't matter if the cost of catching an exception was even 100x the cost of a failed `expected`... if the exception is only thrown one out of every 10,000 executions.

Not to mention the fact that you pay the `expected` cost all the time. Every return value gets bigger. When you get the return value, you have to copy/move it out of the `expected`; not unless you plan on storing it. So you can forget about eliding return values when you use `expected`; you have to do a copy/move. Not unless you're going to use the value in-situ.

So I'd say that your statement requires some actual evidence.

Christopher Horvath

unread,
May 20, 2015, 8:23:05 PM5/20/15
to std-pr...@isocpp.org
Hi, I've just joined this group, and this is my first post.

I'm currently using an implementation of expected<T, E> that is a close match to the proposal, minus a few features.  I work in an environment where exceptions are disallowed, as they are in most game engines or real-time software. Whether or not this is a Good Thing is a separate issue, I think.  Given the starting assumption of not being allowed to use exceptions (and having to catch & disarm all exceptions from underlying libraries), here's a list of options as a coder, of which expected<T,E> is the clear winner:
  • Can't use exceptions, must report errors from function (example: a script had a syntax error, causing parser to fail)
    • abort/assert -> this is obviously bad, but what a surprising amount of code does
    • if function was void, return a std::pair<bool, std::string> as an example error flag/message. if function is not void, return status pair by output reference. Same thing could be done with a tuple on return value only.
      • This approach suffers from having to return a dummy value in the case of an error, which is especially bad if the return type requires work or resources to produce
      • This approach does not force user to check results, error can be ignored.
    • return expected<MyReturnType, std::string>.
      • No dummy return value needed in error case
      • if error exists and is not checked, calling value() throws exception. Although we're trying to avoid this in my scenario, at least there's some effect and error does not silently go by as it might with an error code.
      • if error exists, "why" is present as a string, or other error structure.
      • is clearly visible at call site, so that someone else reading the code can clearly see what's going on
An example usage looks like this:
expected<std::unique_ptr<Mesh>, std::string> readMesh(const std::string& file_name) {
   
try {
       
// geometry library returns mesh by pointer, throws exception on error.
       
return expected<std::unique_ptr<Mesh>, std::string>{std::unique_ptr<Mesh>{geometry::readMesh(file_name)}};
   
} catch (std::exception& exc) {
       
return make_unexpected<std::unique_ptr<Mesh>>(exc.what());
   
} catch (...) {
       
return make_unexpected<std::unique_ptr<Mesh>>("unknown exception, cannot load mesh from file: " + file_name);
   
}
}

Two features that would make this even better would be:
  •  being able to specify an on_error functional, so that you could specify that it should abort rather than use exceptions, for the case where exceptions are disallowed at the compilation level.
  • being able to require (optionally) that the status be checked on an expected<void, E> before it goes out of scope, or some other mechanism to prevent these errors from being ignored.  This would be helpful even with non-void expected<T, E> cases where the return value isn't used.
I realize that "if we just used exceptions" much of this would go away - but even then there's a design question. Does a parsing error count as an exception? Are there "weak" and "strong" exceptions?  With the expected approach, a lot of this ambiguity goes away from API design, and especially from API usage.

I argue that exceptions and expected happily co-exist, and I'm enjoying using it so far.  It has cleared up a lot of confusion.

Vicente J. Botet Escriba

unread,
May 21, 2015, 2:49:19 AM5/21/15
to std-pr...@isocpp.org
Le 21/05/15 01:58, Thiago Macieira a écrit :
On Wednesday 20 May 2015 12:02:03 Nicol Bolas wrote:
It is the standard way. The question is whether we should also, in some 
cases, have an alternative. And what that alternative should be.
Sorry, I didn't mean to start a discussion about the merits of exceptions 
again.

I'm just going to say that I agree with Nicol's paragraph above. The C++ 
standard has made a choice for exceptions, that's fine. All I'm asking is that 
there should be some alternative in some cases.

The monad error expected<T> is not an alternative to exceptions, but to the other ways used to report errors without exceptions. Either we have non of them either we need to select a better way.

If someone wants to discuss the merits/demerits of exceptions,
could s/he start the discussion on a new thread, please?

Thanks,
Vicente

Vicente J. Botet Escriba

unread,
May 21, 2015, 3:06:41 AM5/21/15
to std-pr...@isocpp.org
Le 20/05/15 20:09, Nicol Bolas a écrit :
On Wednesday, May 20, 2015 at 2:05:31 PM UTC-4, Tony V E wrote:
On Wed, May 20, 2015 at 11:08 AM, Vicente J. Botet Escriba <vicent...@wanadoo.fr> wrote:
Le 19/05/15 03:01, Nicol Bolas a écrit :

I think at this point, we should focus on getting the core feature: parsing strings via string_view. And those many malign the FileSystem TS solution, it is prior art on dealing with error codes in standard library C++.

I don't share the FileSystem design to report errors. I have used it in Boost.Chrono, and it really doesn't scale. It is difficult to say it is prior art when we include it in the FS TS. For me, it is the temporary C++ standard way of defining interfaces that report errors without using exceptions until we have a better way.

Vicente


Yes I really think we should use FS as an example of "there must be a better way to do this".  To me that is either:

- just use exceptions
or
- something like expected<>

The FS should have half as many functions as it currently has.  I hope that is fixed before standardization.

I personally have no complaints about the filesystem's method of doing error codes. But I also wouldn't be adverse to seeing `expected` used instead of error code parameter outputs.

But one thing I'm adamantly against is being forced to use error codes. They should be optional, not mandatory. In C++, exceptions are the standard way of signaling errors; I shouldn't have to deal with error codes unless I choose to.

So I would say that filesystem should keep the same number of functions. Either that, or it shouldn't support error codes at all.


I understand that some of us want to be able to use exceptions instead or error codes and that the code is as readable as it can.
The idiom

    auto i = return_expected().value();

to throw an exception, is not as readable as without the call to value().

I have not a solution to the duplication of functions and expected doesn't try to solve it. I we need to have an interface that avoids exceptions, expected is a good candidate.

Vicente


Olaf van der Spek

unread,
May 21, 2015, 7:33:14 AM5/21/15
to std-pr...@isocpp.org
On Wednesday, May 20, 2015 at 8:09:20 PM UTC+2, Nicol Bolas wrote:
But one thing I'm adamantly against is being forced to use error codes. They should be optional, not mandatory. In C++, exceptions are the standard way of signaling errors; I shouldn't have to deal with error codes unless I choose to.

Others are against being forced to use exceptions.. They should be optional, not mandatory. Are exceptions the standard for errors? I don't know. What's an error?

For example AFAIK iostream doesn't throw if it can't open a file.


Patrice Roy

unread,
May 21, 2015, 8:13:12 AM5/21/15
to std-pr...@isocpp.org
There are legitimate use cases for both. Given the importance of constructors, exceptions are not going to go away, and this does not make tools like optional or expected any less useful in their own right.

For parsing, I think comparing I/O examples in C++ (streams signal problems through error states, unless the client code explicitly requests this to be done through exceptions) and in Java (mandatory exception checking) give us a clue that systematic exception checking leads to much more complex client code even in the simplest cases (I'll spare you examples even though they really make C++ shine :) ). Setting streams in such a way as to signal problems through exceptions, though, might be appropriate in some situations. My personal preference would be to follow the same path: errors in general, expections if explicitly requested by the client code. It would seem to be philosophically in line with existing practice.


Nicol Bolas

unread,
May 21, 2015, 9:19:41 AM5/21/15
to std-pr...@isocpp.org
On Thursday, May 21, 2015 at 7:33:14 AM UTC-4, Olaf van der Spek wrote:
On Wednesday, May 20, 2015 at 8:09:20 PM UTC+2, Nicol Bolas wrote:
But one thing I'm adamantly against is being forced to use error codes. They should be optional, not mandatory. In C++, exceptions are the standard way of signaling errors; I shouldn't have to deal with error codes unless I choose to.

Others are against being forced to use exceptions..

I don't have to care what they're against, because exceptions are standard.

Are exceptions the standard for errors?

Yes, they are; just look at the C++ standard library. They are not always be used in every case; in accord with the iostream library's seeming desire to make the wrong decision at every turn, they make them optional. But they're clearly the primary way errors get reported in the standard library.

Whether you like exceptions or not, the fact that they are the standard mechanism for reporting errors in the C++ standard is not open for debate. Virtually all new libraries are designed around them, and any error code API is available only available as an option, not the default.

From the standard: "Class `error_code` is an adjunct to error reporting by exception."

Matthew Woehlke

unread,
May 21, 2015, 10:25:11 AM5/21/15
to std-pr...@isocpp.org
On 2015-05-20 19:56, 'Matt Calabrese' via ISO C++ Standard - Future
Proposals wrote:
The difference in this case is that if your program is written
correctly, the exception is never thrown. The exception in this case is
not the original error, but failure to check for the error, which is not
a runtime error, but a coding error, i.e. "something bad happened *and*
nothing paid attention".

In fact, one can argue that this would be a compile error. (The counter
argument is that you want to write code where you assume that errors
never occur, i.e. you don't want to have to bother checking for them,
and you don't mind if the program simply aborts if one does.)

--
Matthew

Matthew Woehlke

unread,
May 21, 2015, 10:54:14 AM5/21/15
to std-pr...@isocpp.org
On 2015-05-20 20:22, Nicol Bolas wrote:
> On Wednesday, May 20, 2015 at 7:30:06 PM UTC-4, Matthew Woehlke wrote:
>> The cognative overhead of catching
>> exceptions vs. just checking something like a std::expected is high.
>
> ... how, exactly?

auto result = parse(input);

if (result)
do_something(*result);

do_something_else();

if (result)
do_something_again(*result);

- vs -

T result; // grr, can't use auto
auto is_valid = false;

try {
result = parse(input);
is_valid = true;
}
catch(/* what goes here? */) { /* don't care */ }

if (is_valid)
do_something(result);

do_something_else();

if (is_valid)
do_something_again(*result);

So... a simple if() vs. a try-catch that requires me to know what I am
supposed to be catching and potentially messes up my scope and/or
control flow.

> You can't possibly talk about the performance of something without cover
> the actual circumstances of the specific instance. It doesn't matter if the
> cost of catching an exception was even 100x the cost of a failed
> `expected`... if the exception is only thrown one out of every 10,000
> executions.

Yes, but what if failure is frequent? In file parsing, this might not be
unusual. For that matter, what if I'm parsing a file whose fields may or
may not be numbers, and the way I determine that is by trying to parse
the field as a number and seeing if it succeeds? (I can certainly
imagine this being more efficient than parsing the field twice, once to
see if it "looks like" a number, then again to get the value... hoping
on the second pass that I didn't miss something.)

This reminds me of something I saw recently (not sure if it was this
group) about not spending resources on malicious actions... but that's
exactly what exceptions do; turn the case of bad input into the most
expensive code path.

> Not to mention the fact that you pay the `expected` cost all the time.
> Every return value gets bigger. When you get the return value, you have to
> copy/move it out of the `expected`; not unless you plan on storing it. So
> you can forget about eliding return values when you use `expected`; you
> have to do a copy/move. Not unless you're going to use the value in-situ.
>
> So I'd say that your statement requires some actual evidence.

I'd say that for your statement also. I could easily write a highly
optimized `expected<T, exception_ptr>` whose size is only sizeof(T) +
sizeof(void*) and whose move ctor consists of a POD assignment of a T
and a void*, and a clear of a void*. (And the compiler ought to be able
to optimize away the dtor of the old instance.) *And* you're asserting
that RVO is not possible for that? (Why?)

I can write an even better `expected<T, ErrorEnum>`... it's standard
layout and trivially copyable with size sizeof(T) + sizeof(ErrorEnum).

--
Matthew

Matthew Fioravante

unread,
May 21, 2015, 11:34:27 AM5/21/15
to std-pr...@isocpp.org, mw_t...@users.sourceforge.net

It really depends on what you're trying to do. 

If failure is frequent and you need to handle each individual failure differently then exceptions get in the way. Its much easier (and also possibly more efficient) to check a return code with if() instead of setting up separate try/catch blocks around each call. The specific error handling for each situation is also localized to where the error occurs, improving readability.

The one case where I've seen exceptions really shine is when you have an all or nothing situation where you want to try a bunch of steps and just bail out completely the same way if any one of them fails. For example you want to do a bunch of file IO routines, or parse a text file, or do a series of system calls, etc..

I've had to write plenty of systems C code which do system call after system call and have to carefully check the return value of each one (not to mention careful use of goto for resource cleanup since we don't have RAII!). Its extremely painful and usually you end up writing a macro wrapper which will call the function, check for failure, and log/return/goto if it failed . Having exceptions with a try/catch block around the whole series of calls really improves this situation dramatically because the error handling is identical for each call and with try/catch you can specify it only once.


This reminds me of something I saw recently (not sure if it was this
group) about not spending resources on malicious actions... but that's
exactly what exceptions do; turn the case of bad input into the most
expensive code path.

If the case of bad input is rare (exceptional), then this can actually be a good thing. If bad input is common, then you might be pessimizing your code.
 

> Not to mention the fact that you pay the `expected` cost all the time.
> Every return value gets bigger. When you get the return value, you have to
> copy/move it out of the `expected`; not unless you plan on storing it. So
> you can forget about eliding return values when you use `expected`; you
> have to do a copy/move. Not unless you're going to use the value in-situ.

I think this problem exists for any function that returns multiple values, whether its struct like (i.e. tuple, pair, array)  or union like (i.e. variant, optional, expected).

It might still be possible for the compiler to optimize in some cases:

auto x = foo().value();

In the above example, since the expected object is never actually kept around and is guaranteed to contain a value if it didn't throw, the optimizer ostensibly could elide a move and make local variable x just reference the memory used by the expected object returned by foo.

If you're just using the value locally and then throwing it away, you can create a reference
auto exp = foo();
if(!exp) {
 
//handle error
}
auto& val = exp.value();

If you're storing the value somewhere externally then all bets are off, you have to pay for at least a move.
 

Matthew Woehlke

unread,
May 21, 2015, 11:48:07 AM5/21/15
to std-pr...@isocpp.org
On 2015-05-21 11:34, Matthew Fioravante wrote:
> On Thursday, May 21, 2015 at 10:54:14 AM UTC-4, Matthew Woehlke wrote:
>> On 2015-05-20 20:22, Nicol Bolas wrote:
>>> On Wednesday, May 20, 2015 at 7:30:06 PM UTC-4, Matthew Woehlke wrote:
>>>> The cognative overhead of catching
>>>> exceptions vs. just checking something like a std::expected is high.
>>>
>>> ... how, exactly?
>> [...]
>>> You can't possibly talk about the performance of something without cover
>>> the actual circumstances of the specific instance. It doesn't matter if
>>> the cost of catching an exception was even 100x the cost of a
>>> failed `expected`... if the exception is only thrown one out of
>>> every 10,000 executions.
>>
>> Yes, but what if failure is frequent?
>
> It really depends on what you're trying to do.
>
> If failure is frequent and you need to handle each individual failure
> differently then exceptions get in the way. Its much easier (and also
> possibly more efficient) to check a return code with if() instead of
> setting up separate try/catch blocks around each call. The specific error
> handling for each situation is also localized to where the error occurs,
> improving readability.

I could point out that you can individually wrap each call in its own
try/catch, but as I was trying to illustrate, that's likely to be
detrimental to readability :-).

Actually, you caught the point I missed in my previous mail, responding
to Nicol's question about cognative load... an if() checking the result
at the point where you obtain it is easy to follow. Having to dig down
an unknown number of layers of control flow to find an enclosing
try/catch is much, much harder. Especially when exceptions start making
it further up the stack before being caught, possibly in many different
and widely varying locations.

> The one case where I've seen exceptions really shine is when you have an
> all or nothing situation where you want to try a bunch of steps and just
> bail out completely the same way if any one of them fails. For example you
> want to do a bunch of file IO routines, or parse a text file, or do a
> series of system calls, etc..

...so just don't check if your std::expected is valid :-).

This (not talking to you - Matthew F. - specifically any more) is why I
don't understand why the exception-worshippers object so strongly to
std::expected. If you prefer exceptions, *you can have exceptions*,
almost trivially. They just aren't *forced* on you.

Conversely, the burden of forcing exceptions on those of us that don't
want exceptions is much heavier.

--
Matthew

Nicol Bolas

unread,
May 21, 2015, 12:35:43 PM5/21/15
to std-pr...@isocpp.org, mw_t...@users.sourceforge.net
On Thursday, May 21, 2015 at 10:54:14 AM UTC-4, Matthew Woehlke wrote:
On 2015-05-20 20:22, Nicol Bolas wrote:
> On Wednesday, May 20, 2015 at 7:30:06 PM UTC-4, Matthew Woehlke wrote:
>> The cognative overhead of catching
>> exceptions vs. just checking something like a std::expected is high.
>
> ... how, exactly?

  auto result = parse(input);

  if (result)
    do_something(*result);

  do_something_else();

  if (result)
    do_something_again(*result);

- vs -

  T result; // grr, can't use auto
  auto is_valid = false;

  try {
    result = parse(input);
    is_valid = true;
  }
  catch(/* what goes here? */) { /* don't care */ }

  if (is_valid)
    do_something(result);

  do_something_else();

  if (is_valid)
    do_something_again(*result);

Nobody writes exception handling code like that, for the simple and obvious reason that nobody would want to.

You'd do this:

try
{
 
auto result = parse(input);
  do_something
(result);
  do_something_else
();
  do_something_again
(result);
}
catch(<insert exception here>)
{
  do_something_else
();
}

Alternatively, you could simply move `do_something_else` outside of the condition, unless there's a reason why `do_something_else` absolutely must come before do_something_again`. In which case, maybe "else" shouldn't be part of the name ;)

Also, what happens if you need to handle different kind of errors? After all, `parse` might fail for multiple reasons. The input could fail to match. Or the input couldn't be read from the stream (aka: a filesystem error). The latter requires that you terminate the parsing operation, which must be handled at a higher level than this mere function.

What does your code look like then? How do you locally handle the input-failure error, while still propagating the filesystem error to the code that can actually terminate the parse operation?
 
So... a simple if() vs. a try-catch that requires me to know what I am
supposed to be catching and potentially messes up my scope and/or
control flow.

Ignoring the inaccuracy of the latter clause, knowing what you are supposed to catch is... well, par for the course. See the above scenario, where the reason for the failure materially affects how the user should react to it.

> You can't possibly talk about the performance of something without cover
> the actual circumstances of the specific instance. It doesn't matter if the
> cost of catching an exception was even 100x the cost of a failed
> `expected`... if the exception is only thrown one out of every 10,000
> executions.

Yes, but what if failure is frequent? In file parsing, this might not be
unusual. For that matter, what if I'm parsing a file whose fields may or
may not be numbers, and the way I determine that is by trying to parse
the field as a number and seeing if it succeeds?

Then that would be a matter of your parsing interface. Such a function might be called "parse_possible_integer" or whatever. The function, by its contract with the caller, recognizes the possibility that the text may not be parsable as an integer.

There's a conceptual difference between "convert this text to an integer" and "if this text is an integer, convert it". These are two different functions.

This reminds me of something I saw recently (not sure if it was this
group) about not spending resources on malicious actions... but that's
exactly what exceptions do; turn the case of bad input into the most
expensive code path.

"Bad input" is not necessarily "malicious". Indeed, it rarely is.

> Not to mention the fact that you pay the `expected` cost all the time.
> Every return value gets bigger. When you get the return value, you have to
> copy/move it out of the `expected`; not unless you plan on storing it. So
> you can forget about eliding return values when you use `expected`; you
> have to do a copy/move. Not unless you're going to use the value in-situ.
>
> So I'd say that your statement requires some actual evidence.

I'd say that for your statement also. I could easily write a highly
optimized `expected<T, exception_ptr>` whose size is only sizeof(T) +
sizeof(void*) and whose move ctor consists of a POD assignment of a T
and a void*, and a clear of a void*.

... so? That's still more expensive than elision. Also, that wasn't the expense I was talking about. Also, I'm pretty sure `exception_ptr` needs more than just a pointer.

Also, a "POD assignment of a T" requires that T be a POD (or whatever you're talking about. I think you mean "trivially copyable"). Which is certainly not something any `expected` class should require. It's one thing to use optimizations where available. It's quite another to enforce arbitrary restrictions so that `expected` always has that performance.

Also, why do you think that the compiler-generated copy/move constructor for `T` would be slower than a "POD assignment", if `T` were trivially copyable?
 
(And the compiler ought to be able
to optimize away the dtor of the old instance.) *And* you're asserting
that RVO is not possible for that? (Why?)

You can elide the return of the `expected`. But you still have to get the value out of the `expected` value. You can get it by reference, but that's only viable if you're not going to store that data anywhere else. If you are, then you need to copy/move it.

Consider the use of such a thing in the initialization of a class data member.

class Stuff
{
  T t = Function().value();
};

If that were just `T t = Function()`, the copy from the return value could be elided. However, since I have to go through `expected`, no elision is possible.
 
I can write an even better `expected<T, ErrorEnum>`... it's standard
layout and trivially copyable with size sizeof(T) + sizeof(ErrorEnum).

Actually, you can't. `expected` needs some data to know whether it's T or `ErrorEnum`. Otherwise, the user can't tell the difference between the error state or T being valid. So it needs at least one bit more than the sum of the two.

Not unless you anoint some specific value of `ErrorEnum` to be "not an error". Whick makes `ErrorEnum` something of a misnomer.

Also... I don't see how making `expected` larger than both of its constituent components is "better". Just because something is standard layout and trivial (as much as `T` or `EnumError` allow) doesn't make it "better".

Nicol Bolas

unread,
May 21, 2015, 12:44:32 PM5/21/15
to std-pr...@isocpp.org, mw_t...@users.sourceforge.net

But that's what makes the cognitive load easier with exceptions. The local function does not have to care. It doesn't have to have syntax that says, "if I get a filesystem error, return it. If I get a parsing error, handle it like this. If I get some other kind of error, handle it like that."

The exceptions that need to be handled locally are handled locally. No syntax needs to be expended to handle non-local errors.

 

> The one case where I've seen exceptions really shine is when you have an
> all or nothing situation where you want to try a bunch of steps and just
> bail out completely the same way if any one of them fails. For example you
> want to do a bunch of file IO routines, or parse a text file, or do a
> series of system calls, etc..

...so just don't check if your std::expected is valid :-).

This (not talking to you - Matthew F. - specifically any more) is why I
don't understand why the exception-worshippers object so strongly to
std::expected.

Well, no "exception-worshippers" are objecting to it; at least, not that I can see. So I don't see what you're talking about.

What is being "objected to" are:

1) The notion that `expected` is a better error reporting syntax than exceptions.

2) The corresponding idea (and arguments in support of the position) that exceptions are bad, wrong, poorly-thought-out-, or otherwise a priori harmful.

3) The idea that the standard should abandon exceptions as the default means of error handling.

If you prefer exceptions, *you can have exceptions*,
almost trivially. They just aren't *forced* on you.

Only not. Because if I'm linking against code that uses arbitrary error handling system X, I now have to convert those error methods into exceptions. That's the nice thing about having a standard way of conveying errors; inter-operation is much easier.

Conversely, the burden of forcing exceptions on those of us that don't
want exceptions is much heavier.

I don't care. The horse is out of the barn and has won the Kentucky Derby; it's too late to close the barn door.

Exceptions are standard. Full Stop.

Matthew Woehlke

unread,
May 21, 2015, 1:39:40 PM5/21/15
to std-pr...@isocpp.org
On 2015-05-21 12:35, Nicol Bolas wrote:
> Also, what happens if you need to handle different kind of errors?

...then you would inspect the error portion of the std::expected. Which
might be an enum. Which *you can switch() on*. Try that with std::exception.

> Or the input couldn't be read from the stream (aka: a filesystem
> error). The latter requires that you terminate the parsing operation, which
> must be handled at a higher level than this mere function.

In that case, you would return from the function. (Or go ahead and throw
an exception, but I am assuming an environment which forbids exceptions.)

> There's a conceptual difference between "convert this text to an integer"
> and "if this text is an integer, convert it". These are two different
> functions.

That feels like an artificial distinction. 'std::expected<T, ParseError>
parse(...)' is an excellent API for both cases.

> ... so? That's still more expensive than elision. Also, that wasn't the
> expense I was talking about. Also, I'm pretty sure `exception_ptr` needs
> more than just a pointer.

Pointer and reference count, and the exception object itself. The latter
can be intrusive, or an additional level of indirection may be used, so
as to incur no cost in the non-error case. (I'm optimizing for the
non-error case, since that's the one you are concerned about.)

> class Stuff
> {
> T t = Function().value();
> };
>
> If that were just `T t = Function()`, the copy from the return value could
> be elided. However, since I have to go through `expected`, no elision is
> possible.

Assuming that std::expected is inline (and it's a template, so it is)
and implemented properly, I expect elision. If not I would consider it a
missed optimization.

> Actually, you can't. `expected` needs some data to know whether it's
> T or `ErrorEnum`. Not unless you anoint some specific value of
> `ErrorEnum` to be "not an error". Whick makes `ErrorEnum` something
> of a misnomer.

I would do exactly that (probably 0, but I would accept 'tricks' like
having a constexpr template that the user can specialize to provide the
special value), and I don't see it as a misnomer at all. In existing
practice, it is common for 0 to indicate 'no error' and non-zero values
to indicate various possible errors.

IMO, the optimization advantage is well worth requiring that for
std::expected<T, EnumType>, a value of EnumType must indicate "no error".

Alternatively, it might be irrelevant. Note that I'm not claiming this
is the BEST implementation. Just that it is a POSSIBLE implementation
which disproves your assertion that std::expected 'can't possibly be
efficient'. Other possible implementations may be equally efficient
without the requirement of a special "no error" value.

--
Matthew

Jeffrey Yasskin

unread,
May 21, 2015, 1:39:41 PM5/21/15
to std-pr...@isocpp.org, mw_triad
On Thu, May 21, 2015 at 9:44 AM, Nicol Bolas <jmck...@gmail.com> wrote:
> Exceptions are standard. Full Stop.

The question here is whether the standard should change to accommodate
a non-exception error reporting mechanism. Just repeating that the
standard currently does not include that other mechanism, doesn't help
with answering that question.

Ville Voutilainen

unread,
May 21, 2015, 1:52:45 PM5/21/15
to std-pr...@isocpp.org
On 21 May 2015 at 20:39, 'Jeffrey Yasskin' via ISO C++ Standard -
Also, the standard already does include an alternative mechanism -
exception_ptr.
You can use it to convey exceptions not unlike expected would, without
actually throwing
an exception.

The discussion about whether exceptions should or should not be the
only way to report
errors, and what to do on platforms where exceptions are prohibitively
expensive, seem
to miss part of the point of what expected is for. Part of its
motivation is for cases where
throwing an exception in the middle of processing something is so
annoying that it will
uglify the code beyond sanity. Such cases include collecting results
from multiple sub-tasks
into a larger result set and conveying that result set to a client.
The sub-tasks should not
throw their exceptions into that collection step, they should wrap the
exceptions so that
the result set can be built and the user can decide when to handle the
exceptions in the result set.

Francis (Grizzly) Smit

unread,
May 21, 2015, 2:21:15 PM5/21/15
to std-pr...@isocpp.org


On 22/05/15 00:53, Matthew Woehlke wrote:
> On 2015-05-20 20:22, Nicol Bolas wrote:
>> On Wednesday, May 20, 2015 at 7:30:06 PM UTC-4, Matthew Woehlke wrote:
>>> The cognative overhead of catching
>>> exceptions vs. just checking something like a std::expected is high.
>> ... how, exactly?
> auto result = parse(input);

How can this work the compiler cannot know what will be in the value
input a int along a double a long double etc
the

type result parse<type>(input)

idea made sense but this??
also it has the advantage of looking like a sort of cast which it is in
a way,
and then we can have have variants like:

int base = 2;
string tail;

type result = parse<type, base>(input, &tail);

where tail gets the left overs from parsing the input and we can decided
if that's an error or just parse tail for some thing else.
.~. In my life God comes first....
/V\ but Linux is pretty high after that :-D
/( )\ Francis (Grizzly) Smit
^^-^^ http://www.smit.id.au/
-----BEGIN GEEK CODE BLOCK-----
Version: 3.1
GM/CS/H/P/S/IT/L d- s+:+ a++ C++++ UL++++$ P++ L+++$ E--- W++
N W--- M-- V-- PE- PGP t+ 5-- X-- R- tv b++++ D-
G e++ h+ y?
------END GEEK CODE BLOCK------
http://www.geekcode.com/

Matthew Woehlke

unread,
May 21, 2015, 2:49:33 PM5/21/15
to std-pr...@isocpp.org
On 2015-05-21 14:21, Francis (Grizzly) Smit wrote:
>> auto result = parse(input);
>
> How can this work the compiler cannot know what will be in the value
> input a int along a double a long double etc

You're reading too much into the example :-). It's meant to be
illustrative (more like pseudo-code), not compilable.

(And we're off topic; this thread is about std::expected :-).)

--
Matthew

Matthew Woehlke

unread,
May 21, 2015, 3:03:47 PM5/21/15
to std-pr...@isocpp.org
On 2015-05-21 12:44, Nicol Bolas wrote:
> On Thursday, May 21, 2015 at 11:48:07 AM UTC-4, Matthew Woehlke wrote:
>> Actually, you caught the point I missed in my previous mail, responding
>> to Nicol's question about cognative load... an if() checking the result
>> at the point where you obtain it is easy to follow. Having to dig down
>> an unknown number of layers of control flow to find an enclosing
>> try/catch is much, much harder. Especially when exceptions start making
>> it further up the stack before being caught, possibly in many different
>> and widely varying locations.
>
> But that's what makes the cognitive load *easier* with exceptions.

For you, maybe. For me, the potential for code flow to be interrupted at
some arbitrary point and restart at some other point, which is not only
also arbitrary but depends on the state of the call stack, is worse than
being able to follow the control flow directly.

> What is being "objected to" are:
>
> 1) The notion that `expected` is a better error reporting syntax than
> exceptions.

It is, by definition. Exceptions are unacceptable (to some people and/or
in some situations). Thus, *any* functional error reporting mechanism is
better.

> 2) The corresponding idea (and arguments in support of the position) that
> exceptions are bad, wrong, poorly-thought-out-, or otherwise a priori
> harmful.

I understand that you take issue with people that... ah, take issue with
exceptions. However, IMHO the burden is on you to convince us why we are
wrong. I don't see you trying to gently educate us as to why exceptions
are okay.

Until you can convince everyone that currently will not use exceptions
that exceptions are the best thing since classes (*and*, having done
that, also overcome the inertia of decades of code that is not exception
aware), the reality is that we need something else. The goal of
std::expected is to provide a good compromise that satisfies both camps
with minimal overhead.

If you find std::expected intolerable, for whatever reason, please try
constructively to either address its issues, propose an alternative that
is acceptable to both parties, or address the underlying issues that
folks have with exceptions.

> 3) The idea that the standard should abandon exceptions as the default
> means of error handling.

I don't think anyone is proposing to deprecate exceptions where they are
currently used. What we're objecting to is trying to force new and
otherwise desperately needed API on us that *is not usable without
exceptions*. (Which, per the current staus quo, means "not usable, period".)

>> If you prefer exceptions, *you can have exceptions*,
>> almost trivially. They just aren't *forced* on you.
>
> Only not. Because if I'm linking against code that uses arbitrary error
> handling system X, I now have to convert those error methods into
> exceptions. That's the nice thing about having a standard way of conveying
> errors; inter-operation is much easier.

That's a straw man. The current status quo has nothing to do with
whether or not std::expected would help... and in fact, the current
status quo is exactly why we *need* std::expected!

Right now we have a wild west of ways to report errors. std::expected
would give us an obvious standard way to do it where exceptions are not
acceptable, *and* it makes it trivial to promote errors to exceptions;
just pretend that errors never happen.

Try looking at it this way. Lots of people refuse to use exceptions, and
are going to continue to do so. Some of these people are going to write
libraries that are interesting to people that would prefer to use
exceptions. Now, which would you prefer?

int result; // output parameter
alib_do_something(&result, input);
if (auto const e = alib_get_error)
wrap_error_as_exception(e); // will throw

...or...

// return is std::expected; value() will throw on error
auto result = alib_do_something(input).value();

...?

--
Matthew

Matthew Woehlke

unread,
May 21, 2015, 3:09:19 PM5/21/15
to std-pr...@isocpp.org
On 2015-05-21 13:52, Ville Voutilainen wrote:
> The discussion about whether exceptions should or should not be the
> only way to report errors, and what to do on platforms where
> exceptions are prohibitively expensive, seem to miss part of the
> point of what expected is for. Part of its motivation is for cases
> where throwing an exception in the middle of processing something is
> so annoying that it will uglify the code beyond sanity.

Thanks; that's well taken.

Another example would be when I just don't care about errors, i.e. where
it is acceptable to use a default/fallback value if parsing fails. This
is stupid easy with std::expected (value_or()). Without, it may require
writing my own wrapper to catch exceptions and instead return a default
value.

--
Matthew

Tony V E

unread,
May 21, 2015, 4:52:11 PM5/21/15
to Standard Proposals


On Wed, May 20, 2015 at 4:52 PM, 'Matt Calabrese' via ISO C++ Standard - Future Proposals <std-pr...@isocpp.org> wrote:

"expected", or discriminated unions in general, provide many benefits, and some drawbacks, over exceptions:

==========

Feel free to correct anything that is wrong in the above assessment or to add to it. I am not an expert in this domain and these are just my personal thoughts.

That's a pretty good pro/con list.
 

Not that the following has any chance of changing the language, but my personal thoughts have been that C++ exceptions are good, but would ideally be much more like a discriminated union in terms of implementation. By that I mean they still would not affect the return type of the function, as is already the case with current exceptions though not with "expected", but they would deal with a closed set of types rather than an open set. You would be able to query at compile time the set of possible exceptions and append to that set when making higher level functions (important for generic code -- this is also analogous to using noexcept to determine if an expression can throw or not). You'd always be able to effectively make an open set of exception types by using some kind of a polymorphic type (I.E. a type-erased object with a large small-object optimization), though you would lose some of the functionality of C++ exceptions as they are in the language. Exception propagation would continue to work as-is, as would accessing of the normal result of a function. Constructors would also work as easily as they do right now.

This would basically just be C++ exceptions, only implemented on top of a discriminated union instead of an open set of types.


There are problems with a closed set of exceptions, which have been felt with C++ throw(a,b,c) clauses and java checked exceptions, etc.
In particular, I can't call pointer-to-functions or virtual functions, etc, because I can't know the set of possible exceptions.

Sure I could use polymorphic exceptions then, but I suspect we'd end up using them almost everywhere, leaving us where we are now.

Tony

Thiago Macieira

unread,
May 21, 2015, 5:11:41 PM5/21/15
to std-pr...@isocpp.org
On Thursday 21 May 2015 16:52:09 Tony V E wrote:
> There are problems with a closed set of exceptions, which have been felt
> with C++ throw(a,b,c) clauses and java checked exceptions, etc.
> In particular, I can't call pointer-to-functions or virtual functions, etc,
> because I can't know the set of possible exceptions.

Which is a good thing. You need to know what the function you're calling can
throw, regardless of whether you're doing a direct call, indirect or virtual.

The reimplementation of the function cannot suddenly start throwing unexpected
errors. The catch handler may not know how to handle those. New exception
types need to be caught and handled at a lower level, never leak.

Tony V E

unread,
May 21, 2015, 5:22:00 PM5/21/15
to Standard Proposals
On Thu, May 21, 2015 at 5:11 PM, Thiago Macieira <thi...@macieira.org> wrote:
On Thursday 21 May 2015 16:52:09 Tony V E wrote:
> There are problems with a closed set of exceptions, which have been felt
> with C++ throw(a,b,c) clauses and java checked exceptions, etc.
> In particular, I can't call pointer-to-functions or virtual functions, etc,
> because I can't know the set of possible exceptions.

Which is a good thing. You need to know what the function you're calling can
throw, regardless of whether you're doing a direct call, indirect or virtual.

The reimplementation of the function cannot suddenly start throwing unexpected
errors. The catch handler may not know how to handle those. New exception
types need to be caught and handled at a lower level, never leak.
--

So why are java developers moving away from checked exceptions, and C++ has deprecated throws clauses?
Lack of Discipline?

 
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
   Software Architect - Intel Open Source Technology Center
      PGP/GPG: 0x6EF45358; fingerprint:
      E067 918B B660 DBD1 105C  966C 33F5 F005 6EF4 5358

Matt Calabrese

unread,
May 21, 2015, 5:31:53 PM5/21/15
to std-pr...@isocpp.org
On Thu, May 21, 2015 at 1:52 PM, Tony V E <tvan...@gmail.com> wrote:
There are problems with a closed set of exceptions, which have been felt with C++ throw(a,b,c) clauses and java checked exceptions, etc.
In particular, I can't call pointer-to-functions or virtual functions, etc, because I can't know the set of possible exceptions.

Sure I could use polymorphic exceptions then, but I suspect we'd end up using them almost everywhere, leaving us where we are now.

Hey, Tony! 

I don't want to derail too far because I don't think it's realistic that the language would change in this respect anyway, but what are the exact pointer-to-function or virtual function concerns? My thoughts on this are that the exception specification should probably affect the function type, similar to what is now being proposed for noexcept, IIUC. In other words, a function pointer would be able to be a pointer to a function that is noexcept. Or, expanded, a pointer to a function that can possibly throw a particular set of exceptions, also allowing it to bind to something that is a subset of the possible exceptions. Similar constraints would apply to overriding of virtual functions. I don't think that everyone would use polymorphic exceptions everywhere since, in true C++ fashion, we would probably have a way to refer to the list of exception types that an exception could through (similar to using noexcept), and append to that list, allowing even generic code to accumulate a finite set of exception types. Some of this could even then be made automatic in certain situations, similar to the often talked about idea of noexcept(auto). The amount of places where exceptions truly need to be an open set of types, I think, is much less than one might initially expect.

I know that Java has something a bit more similar to this, as you mentioned, but I don't have enough experience in Java to comment on it. I also understand that they are frequently despised, but it's not clear to me that the criticisms are inherent to there being a closed set or simply a matter of poor implementation.

My personal perspective is that I've worked in a world with exceptions, I've worked in a world with expected-like types, and I've worked in a world with a combination of the two. At the very least, in the situations that I've been in personally (anecdotal, I know), I see no reason why everything couldn't have been done with "expected" (and thus, a bounded set of failure kinds) other than that it would have been more verbose using expected than with exceptions, and would have made dealing with generic code considerably more difficult. A closed set of types for exceptions could have effectively provided many of the same benefits of an expected-like type without any of the implementation concerns that come from an open set of exception types.

There are other benefits of a closed set of exception types. Once you have a truly closed set that is known at compile-time, catching of exceptions can then be done with as much power as something like visitation of a discriminated union. By that I mean, since the possible types are known statically and a catch would effectively now be akin to a boost::apply_visitor, you can use the same kind of pattern matching that you use when writing a function template. Quick example:

//////////
template<HRESULT Code>
struct hresult_exception { /**/ };

try { some_windows_function(); }
catch(hresult_exception<AParitcularCode>) { /**/ }
template<HRESULT Code> catch(hresult_exception<Code>) { /**/ }
//////////

Handling groups of exception kinds no longer needs to to rely on those types being related by inheritance, thereby avoiding hierarchies, diamond patterns, and a possible need for virtual inheritance. The actual implementation of exception handling also becomes much simpler now and doesn't require any sophisticated RTTI other than an integer discriminator that is specific to the function that can throw the exception. It also eliminates many of the concerns that people have when exceptions are enabled in their compiler.

I've had this discussion with enough people to realize that I'm in a clear minority here, but I've never been truly satisfied with counter arguments.

Matt Calabrese

unread,
May 21, 2015, 5:33:30 PM5/21/15
to std-pr...@isocpp.org
On Thu, May 21, 2015 at 2:31 PM, Matt Calabrese <cala...@google.com> wrote:
I don't think that everyone would use polymorphic exceptions everywhere since, in true C++ fashion, we would probably have a way to refer to the list of exception types that an exception could through

Woops, mean to say "we would probably have a way to refer to the list of exception types that an expression could throw"

Matt Calabrese

unread,
May 21, 2015, 6:38:43 PM5/21/15
to std-pr...@isocpp.org
On Thu, May 21, 2015 at 2:21 PM, Tony V E <tvan...@gmail.com> wrote:
On Thu, May 21, 2015 at 5:11 PM, Thiago Macieira <thi...@macieira.org> wrote:
On Thursday 21 May 2015 16:52:09 Tony V E wrote:
> There are problems with a closed set of exceptions, which have been felt
> with C++ throw(a,b,c) clauses and java checked exceptions, etc.
> In particular, I can't call pointer-to-functions or virtual functions, etc,
> because I can't know the set of possible exceptions.

Which is a good thing. You need to know what the function you're calling can
throw, regardless of whether you're doing a direct call, indirect or virtual.

The reimplementation of the function cannot suddenly start throwing unexpected
errors. The catch handler may not know how to handle those. New exception
types need to be caught and handled at a lower level, never leak.
So why are java developers moving away from checked exceptions, and C++ has deprecated throws clauses?
Lack of Discipline?

At least on the C++ side of things, old-style dynamic exception specifications are not really the same as having a fixed set of compile-time-introspectible exception types.

I do personally believe that while dealing with a fixed compile-time set is not always trivial, particularly without automatic deduction of such compile-time specifications, it has actual benefits that aren't always acknowledged. In ideal world, I think it is the approach that people should generally prefer, but again, I know that I'm in the minority here.

Perhaps the most intriguing aspect of this from a practical perspective is that much of the rationale for turning off exceptions, which is remarkably common in the real world, would go away. It's sort of embarrassing that there is a feature (really two features) in the language that many users simply turn off, despite now making their implementation nonstandard, and they often do so for actual legitimate reasons. I'm a fan of exceptions in general, but IMO this is a symptom of flaws in how exceptions operate in C++ and shouldn't be too quickly ignored.

Patrice Roy

unread,
May 21, 2015, 7:36:09 PM5/21/15
to std-pr...@isocpp.org
If I may: exceptions are necessary (there are cases that are not solved without them) but do not need to be ubiquitous. Parsing errors are not exceptional, as far as I know, and are probably not a good case for exceptions-by-default. Exceptions are probably a good voluntarily-opting-in in this case, just not the default.

We've had quite a lot of feedback, some informed and some less, on the inappropriateness of exceptions in some cases. It's worth looking into. I think this issue is takin way too much space and time in the current thread, although it does deserve looking into in a different thread (SG14?) since there is resistance to the exception mechanisms in domains where C++ is the language of choice.

Cheers!

Thiago Macieira

unread,
May 21, 2015, 8:53:23 PM5/21/15
to std-pr...@isocpp.org, Tony V E
On Thursday 21 May 2015 17:21:58 Tony V E wrote:
> On Thu, May 21, 2015 at 5:11 PM, Thiago Macieira <thi...@macieira.org>
>
> wrote:
> > On Thursday 21 May 2015 16:52:09 Tony V E wrote:
> > > There are problems with a closed set of exceptions, which have been felt
> > > with C++ throw(a,b,c) clauses and java checked exceptions, etc.
> > > In particular, I can't call pointer-to-functions or virtual functions,
> >
> > etc,
> >
> > > because I can't know the set of possible exceptions.
> >
> > Which is a good thing. You need to know what the function you're calling
> > can
> > throw, regardless of whether you're doing a direct call, indirect or
> > virtual.
> >
> > The reimplementation of the function cannot suddenly start throwing
> > unexpected
> > errors. The catch handler may not know how to handle those. New exception
> > types need to be caught and handled at a lower level, never leak.
> > --
>
> So why are java developers moving away from checked exceptions, and C++ has
> deprecated throws clauses?
> Lack of Discipline?

On the C++ side, because our implementation was never good. As you said, the
exception specification was not carried in function pointers and there was no
provision for variance or contra-variance of exception specifications in
overriding of virtual functions.

What the correct thing should be, I'll have to defer to people who actually
use exceptions and have studied this problem.

My naïve interpretation of the problem is that a function parse_number that
used to throw a restricted set of exceptions shouldn't suddenly start throwing
database_disconnected_exception. What is the caller of parse_number to do when
receiving such an exception that wasn't in the API contract? Let it leak and
thus punt the problem to the higher level? The best case scenario here is
that the application gets to std::terminate. Because it would be a worse
scenario if the application handled it wrongly.

try {
auto port = parse_number<unsigned short>(str);
database.connect(hostname, port);
do_work(database);
} catch (number_out_of_range) {
show("port number out of range");
} catch (database_disconnected_exception &e) {
restart_database(hostname);
}

--

mats.ta...@gmail.com

unread,
May 22, 2015, 6:14:04 AM5/22/15
to std-pr...@isocpp.org

 FYI, Daniel Gutson is apparently working on implementing something not entirely different from this,  (link to Daniel Gutson's post in the Embedded C++ mailing list).

"exact match": catching a base class type of the thrown type does
not success. IOW, no hierarchy traversing
takes place on each catch() construct. This requires minimum (static)
typeinfo data and O(1) matching algorithm.


Matthew Fioravante

unread,
May 22, 2015, 11:36:36 AM5/22/15
to std-pr...@isocpp.org, mats.ta...@gmail.com
Has there been any language that already implemented this style of static closed set exceptions? It does sound like it could make the exception problem much more tractable but I'm sure it has other pitfalls we won't know about until we start trying to develop applications with it.

Nicol Bolas

unread,
May 23, 2015, 1:07:22 PM5/23/15
to std-pr...@isocpp.org
On Wednesday, May 20, 2015 at 11:09:01 AM UTC-4, Vicente J. Botet Escriba wrote:
Le 19/05/15 03:01, Nicol Bolas a écrit :
On Monday, May 18, 2015 at 6:30:38 PM UTC-4, Jeffrey Yasskin wrote:
On Mon, May 18, 2015 at 2:57 PM, Nicol Bolas <jmck...@gmail.com> wrote:
> I just want to see the "sto*" functions get string_view ASAP. We shouldn't
> wait on `expected` just to accomplish that.

That seems like a straightforward paper to get through the committee.
If you write it, I'll try to prevent the group from creeping its
scope.

Actually, that paper already exists (N4015), with a revision (N4109). According to Vicente Escriba, who both wrote those proposals and replied here, scope creep has already started. Also, N4109 has been a while ago (almost a year now), with no followup paper from any discussions based on it.

Yes its me. There is an error on the web page that I have already reported. My name is Vicente J. Botet Escriba. Vicente J. is my fisrt name and Botet Escriba is my last name.

The expected proposal was blocked as the expected proposal was proposing an alternative way to report errors and the standard C++ has already one: exceptions. A study of all the alternative ways to reporting errors was requested so we can take a decision on which ones could be used once the advantages and liabilities of each approach are detailed. I'm not a paper writer and writing such a paper needs to be done carefully and it would takes time. If there are any volunteers to write it, please do it, it will be a pleasure to help. Of course, I will be very happy is this paper unblocks the expected proposal.

To help support this, I have written the attached rough draft document. It attempts to be a survey of as many error reporting mechanisms as possible. This document is attempting to be purely factual, with as little bias as possible. As such, it doesn't have a "pros/cons" list, as what might be a "pro" for one application is a "con" for another. It tries to be descriptive of each method, along several use cases, without stumping for or arguing against any particular one.

Note that the document is fairly lengthy, as there are quite a few ways to go about reporting errors to users, and there are a lot of consequences for difference schemes of error handling and resolution.

As this is a very rough draft, I'm interested in commentary in the following areas:

* What error reporting mechanisms have I missed?

* I examine all of the error mechanisms in the context of 3 broad use-cases: 1) what you do to resolve errors locally, 2) what you do to resolve errors non-locally even across threads, 3) what you do to ignore an error entirely, with a discussion of how you might find places where you've ignored an error by accident. What other dimensions/use cases could be useful for analyzing them?

* When examining one of the error mechanisms, have I missed some important way that it interacts with one of the use cases?

* General mistakes/misconceptions/gross biases/rampaging stupidities at work.
Survey of Error Handling.html

Bjorn Reese

unread,
May 23, 2015, 2:16:48 PM5/23/15
to std-pr...@isocpp.org
On 05/23/2015 07:07 PM, Nicol Bolas wrote:

> * What error reporting mechanisms have I missed?

Networking TS (N4478) passes the return value of asynchronous
operations as parameters in handlers (callbacks), something along the
lines of:

void async_operation(input arguments, [](output arguments) {});

So when the operation returns an std::size_t, then the handler has this
signature:

void handler(std::error_code, std::size_t);

Nicol Bolas

unread,
May 23, 2015, 2:31:55 PM5/23/15
to std-pr...@isocpp.org

It's funny. I had a section on a scheme somewhat like that (based on OpenGL's). But I shortened to just an adendum to the out-of-band error section. I decided that, since there was no way to actually resolve the error (the code that issued the call that provoked the error wouldn't be informed), it wasn't a viable option for people looking for a way to let users resolve errors.

I hadn't really considered the utility of the concept as a way of resolving errors within an operation that was fundamentally asynchronous. I'll take a look at the networking proposal and add an appropriate section on this kind of error reporting.

Edward Catmur

unread,
May 23, 2015, 5:10:32 PM5/23/15
to std-pr...@isocpp.org
> What error reporting mechanisms have I missed?

You haven't mentioned errno; although I'm not sure it counts as a separate mechanism, it's odd not to see it mentioned. While it's largely an adjunct to "special return value", it can also function as an out of band error code:

> For some system calls and library functions (e.g., getpriority(2)),
> -1 is a valid return on success. In such cases, a successful return
> can be distinguished from an error return by setting errno to zero
> before the call, and then, if the call returns a status that
> indicates that an error may have occurred, checking to see if errno
> has a nonzero value.
- http://man7.org/linux/man-pages/man3/errno.3.html

Another important mechanism, though without support in modern languages, is FORTRAN-style alternate return - passing in (an) alternative program location(s) to return to. Dijkstra disliked it, of course, along with multiple entry, but with appropriate syntactic structure I think it could actually work in C++. See http://programmers.stackexchange.com/a/118793

Also, how would you classify setjmp/longjmp? I'm fairly sure there are still some C libraries that use it as their preferred error handling technique. Clearly it's similar to exceptions in that both are stack based non local return, but there's plenty of differences, unwinding being just one.

Nicol Bolas

unread,
May 23, 2015, 5:30:51 PM5/23/15
to std-pr...@isocpp.org


On Saturday, May 23, 2015 at 5:10:32 PM UTC-4, Edward Catmur wrote:
> What error reporting mechanisms have I missed?

You haven't mentioned errno; although I'm not sure it counts as a separate mechanism, it's odd not to see it mentioned. While it's largely an adjunct to "special return value", it can also function as an out of band error code:

> For some system calls and library functions (e.g., getpriority(2)),
>       -1 is a valid return on success.  In such cases, a successful return
>       can be distinguished from an error return by setting errno to zero
>       before the call, and then, if the call returns a status that
>       indicates that an error may have occurred, checking to see if errno
>       has a nonzero value.
- http://man7.org/linux/man-pages/man3/errno.3.html


`errno` is purely an out-of-band error reporting scheme. However, you could combine any out-of-band scheme with the special return values. The SRV signals the error, and the out-of-band state explains its nature. I should mention that when discussing SRVs.
 
Another important mechanism, though without support in modern languages, is FORTRAN-style alternate return - passing in (an) alternative program location(s) to return to. Dijkstra disliked it, of course, along with multiple entry, but with appropriate syntactic structure I think it could actually work in C++. See http://programmers.stackexchange.com/a/118793

Since such a thing doesn't really work in C++ as it currently stands, I don't think it's appropriate for discussion. I'm not trying to catalog every possible mechanism for handling errors, only the ones that can be used in C++.
 
Also, how would you classify setjmp/longjmp? I'm fairly sure there are still some C libraries that use it as their preferred error handling technique. Clearly it's similar to exceptions in that both are stack based non local return, but there's plenty of differences, unwinding being just one

Since using longjmp in C++ will very easily invoke undefined behavior, I'm not sure that a discussion of it is warranted. Yes, many C libraries use it, but C++ cannot. At least, not without an exceptional degree of care.

Vicente J. Botet Escriba

unread,
May 29, 2015, 1:41:29 AM5/29/15
to std-pr...@isocpp.org
Le 23/05/15 19:07, Nicol Bolas a écrit :
Hi and thanks for writing this report.

There is a specialization of the variant return type that makes easier the error propagation. This needs some language changes as e.g. the proposed await/yield operator. This syntactic sugar approach helps on error propagation (non-local error resolution) in an almost transparent way, so the user doesn't need to check directly if there is an error or not in order to propagate it. All it needs is to say using the await operator if the expression can return a error or not.

auto exp_value = await TargetFunc(...);

The await operator is telling the compiler that the result of the expression contains possibly an error that needs some specific handling (See the resumable functions proposal). Even if the main case of the resumable functions proposal is to manage asynchronous cases, there is no reason to don't use it synchronously. Gor has already show this cases in his proposal.

Even if more complex, the idea is that the function must return some wrapped type W<T>.
The await expression
	auto exp_value = await TargetFunc(...);
is transformed to something like
	auto exp_value = TargetFunc(...);
	if(!exp_value)
	{
    		return exp_value.error();
	}

and the yield expression

    yield a;

makes use in some way of the explicit constructor of the return type.

    return W<T>(a);
 
IMO, the major advantage to returning a variant type is that we can add syntactic sugar on top of this general interface.
The single question is how easy is it to tell to the compiler that your wrapped type supports the required model.

In a pure functional language as Haskell the single thing required is to be an instance of a Monad. C++ is much more complex and needs a more complex model as the await and yield operator can appear in in place of any expression or return statement respectively. This means that restricting the places where these operators can be used can require a simpler and why not more efficient mapping. These could provided as a QOI of the compiler implementation.
 
If the resumable functions proposal were adopted by the C++ standard, we will see a lot of libraries that will adapt their types to this new mechanism. I don't see how the standard library would not do the same.

Vicente

Nicol Bolas

unread,
May 29, 2015, 3:06:00 AM5/29/15
to std-pr...@isocpp.org

I admit that my knowledge of this feature is limited to a presentation Herb made well over a year ago, when talking about Visual Studio having a preliminary implementation of "async/await". Obviously, things have progressed in a rather different direction, since one of the features (async) isn't there anymore and something else is (yield). So feel free to correct me if I'm wrong.

But isn't issuing some form of coroutine at least marginally expensive? Compared to a regular function call, that is.

I mean, let's take the case of a generalized "parse" function, to which you pass a generalized file IO system. So you're parsing from a file.

The code using the parse function will `await` on it. But the parse function should also `await` on the file IO system it's been given, since that can error too. Any errors it gets are propagated to the caller. So that's two `await` overheads, all just to propagate the error.

By contrast, the overhead for exceptions is paid for with `try` blocks, at the site where you're actually resolving the error. And the runtime cost is paid only when you actually throw, not simply because you might throw. And you don't have to stick `await` in every place that might result in an error.

I don't really see the advantage over exceptions here.

Gor has already show this cases in his proposal.

Even if more complex, the idea is that the function must return some wrapped type W<T>.
The await expression
	auto exp_value = await TargetFunc(...);
is transformed to something like
	auto exp_value = TargetFunc(...);
	if(!exp_value)
	{
    		return exp_value.error();
	}

and the yield expression

    yield a;

makes use in some way of the explicit constructor of the return type.

    return W<T>(a);
 
IMO, the major advantage to returning a variant type is that we can add syntactic sugar on top of this general interface.

Is that what is actually part of that proposal, or is that what you're suggesting should be done? If it's the latter, I would be very much against that. You shouldn't try to slap unrelated constructs into other proposals just because they might be able to work there. Resumable functions is about functions that can be suspended and resumed; giving them special properties, such that the compiler actually generates code that has certain expectations on the user (like the return type of the current function) feels really arbitrary.

Especially when we already have an adequate solution to the problem of returning errors to locations in code defined by the caller. One that requires zero local syntax.

Also, if you can't get people to let `return {x};` use explicit constructors, I fail to see how you can get them to agree that `yield x;` will. That'd be remarkably inconsistent.

If the resumable functions proposal were adopted by the C++ standard, we will see a lot of libraries that will adapt their types to this new mechanism. I don't see how the standard library would not do the same.

As I understand what you're discussing, the await/yield syntax you're suggesting is a form of syntactic sugar which is useful for propagating an error to a higher level with less apparent code. So while the direct syntactic burden at the local level is lessened (no need for an explicit conditional), the rest of it is still there.

So what you're suggesting would only be widely adopted if the reason everyone was avoiding `expected` beforehand was due to having to explicitly test the conditional, just to propagate the error to the higher level.

I don't think that's the case. I'm rather sure that `expected` will be widely adopted or not based on its own merits, not this specific scenario.

Vicente J. Botet Escriba

unread,
May 29, 2015, 1:19:05 PM5/29/15
to std-pr...@isocpp.org
Le 29/05/15 09:05, Nicol Bolas a écrit :


On Friday, May 29, 2015 at 1:41:29 AM UTC-4, Vicente J. Botet Escriba wrote:
Le 23/05/15 19:07, Nicol Bolas a écrit :
On Wednesday, May 20, 2015 at 11:09:01 AM UTC-4, Vicente J. Botet Escriba wrote:
Le 19/05/15 03:01, Nicol Bolas a écrit :



Hi and thanks for writing this report.

There is a specialization of the variant return type that makes easier the error propagation. This needs some language changes as e.g. the proposed await/yield operator. This syntactic sugar approach helps on error propagation (non-local error resolution) in an almost transparent way, so the user doesn't need to check directly if there is an error or not in order to propagate it. All it needs is to say using the await operator if the expression can return a error or not.

auto exp_value = await TargetFunc(...);

The await operator is telling the compiler that the result of the expression contains possibly an error that needs some specific handling (See the resumable functions proposal). Even if the main case of the resumable functions proposal is to manage asynchronous cases, there is no reason to don't use it synchronously.

I admit that my knowledge of this feature is limited to a presentation Herb made well over a year ago, when talking about Visual Studio having a preliminary implementation of "async/await". Obviously, things have progressed in a rather different direction, since one of the features (async) isn't there anymore and something else is (yield). So feel free to correct me if I'm wrong.

But isn't issuing some form of coroutine at least marginally expensive? Compared to a regular function call, that is.
It works with coroutines, but not only. See below.


I mean, let's take the case of a generalized "parse" function, to which you pass a generalized file IO system. So you're parsing from a file.

The code using the parse function will `await` on it. But the parse function should also `await` on the file IO system it's been given, since that can error too. Any errors it gets are propagated to the caller. So that's two `await` overheads, all just to propagate the error.

IMO, the await on the file IO would be used more to improve the performances than to transport errors.
The await on the file IO will suspend, but I'm not sure the await on the parser needs to suspend. All this depends on whether you want to run the parsing concurrently or not. The await on the file IO is natural, so that we don't block the thread.

Note that the parser function on a string wouldn't suspend at all. It is the nature of the IO device that incur in a suspension when await is used (or a wait otherwise).


By contrast, the overhead for exceptions is paid for with `try` blocks, at the site where you're actually resolving the error. And the runtime cost is paid only when you actually throw, not simply because you might throw. And you don't have to stick `await` in every place that might result in an error.
Wouldn't your parser function using exceptions use "await" also on the file IO?

You are right that the use of await everywhere is something that the user must pay for. When doing assignments there is a possible syntactic sugar, replacing

	auto exp_value = await TargetFunc(...);
by
	auto exp_value := TargetFunc(...);
Note that I'm not proposing anything here.


I don't really see the advantage over exceptions here.
I'm not comparing it to exceptions but to error codes. Of course, performance figures are always welcome and needed.

Gor has already show this cases in his proposal.

Even if more complex, the idea is that the function must return some wrapped type W<T>.
The await expression
	auto exp_value = await TargetFunc(...);
is transformed to something like
	auto exp_value = TargetFunc(...);
	if(!exp_value)
	{
    		return exp_value.error();
	}

and the yield expression

    yield a;

makes use in some way of the explicit constructor of the return type.

    return W<T>(a);
 
IMO, the major advantage to returning a variant type is that we can add syntactic sugar on top of this general interface.

Is that what is actually part of that proposal, or is that what you're suggesting should be done?
I said "in some way". That means that I don't included the details.

If it's the latter, I would be very much against that.
So if it is in the proposal you are for and if it is me that I'm suggesting what should be done you are against. I'm sure you wanted to say something else. Anyway, to what you would be against and why?

You shouldn't try to slap unrelated constructs into other proposals just because they might be able to work there.
I believe that the proposal includes this kind of usage, maybe Gor can infirm.

Resumable functions is about functions that can be suspended and resumed; giving them special properties, such that the compiler actually generates code that has certain expectations on the user (like the return type of the current function) feels really arbitrary.
I believe that the proposal takes in account this synchronous case, and in this case the call to suspend does nothing.


Especially when we already have an adequate solution to the problem of returning errors to locations in code defined by the caller. One that requires zero local syntax.
Again, I'm not comparing it to exceptions, but to interfaces using directly error codes.


Also, if you can't get people to let `return {x};` use explicit constructors, I fail to see how you can get them to agree that `yield x;` will. That'd be remarkably inconsistent.
I believe the proposal includes this. I don't remember if with return or with yield.


If the resumable functions proposal were adopted by the C++ standard, we will see a lot of libraries that will adapt their types to this new mechanism. I don't see how the standard library would not do the same.

As I understand what you're discussing, the await/yield syntax you're suggesting
It is not me that suggested that syntax, but Gor & all. It corresponds to the C++ way for the monadic do notation adapted to C++. It is more powerful than the do notation, and as consequence the mapping is more complex. But this mapping is done one for all.

is a form of syntactic sugar which is useful for propagating an error to a higher level with less apparent code. So while the direct syntactic burden at the local level is lessened (no need for an explicit conditional), the rest of it is still there.

Right. The await/yield syntax could be adapted to any monad. As expected can be seen as one monad it can be adapted. But await/yield can be used with other monads that are not used to transport errors.  While the original proposal worked only with functions returning futures, the last version adapted it to work with functions that return any type that can be mapped to some specific contraints.

So what you're suggesting would only be widely adopted if the reason everyone was avoiding `expected` beforehand was due to having to explicitly test the conditional, just to propagate the error to the higher level.
I don't think this is the single reason, but it could be a reason to dislike expected, future, ...
The C++community use to program using imperative style. The syntactic sugar makes it more adapted to this style.


I don't think that's the case. I'm rather sure that `expected` will be widely adopted or not based on its own merits, not this specific scenario.

I believe expected is useful by itself. However its monadic nature makes it useful in more contexts than when we look at it alone. At the end, I worry more for the reasons why it is not adopted than the reasons explaining why it is adopted.

Haskell.Scala/F# have added some kind of syntactic sugar because it simplifies a lot the reading of the code. C++ is not Haskell/Scala/F#, but once you have this syntactic sugar I believe that most of the users will prefer to use it.


Vicente

Nicol Bolas

unread,
May 29, 2015, 8:04:57 PM5/29/15
to std-pr...@isocpp.org
Er, it seems clear that I don't really understand the resumable functions proposal sufficiently well to discuss it intelligently. I assumed that `await`ing on a function meant that you were calling a coroutine or something (regardless of whether it's synchronous or asynchronous).

Since I don't understand what the keywords actually do, how resumable functions work, or anything of the like, I can't really talk about how they impact error handling with regard to optional/expected.

I'll take some time in the future to learn more about it before commenting further.

Vicente J. Botet Escriba

unread,
Nov 11, 2015, 1:39:13 PM11/11/15
to std-pr...@isocpp.org
Hi Nicol,

I come back to this old thread in case you are interested in completing the report in order to sent it to the standard committee.
We could try to see with Gor how the await operator helps in this use case (I'm sure he will be interested).

Vicente

BTW, Have you update the report with the feedback you got?




Nicol Bolas

unread,
Nov 11, 2015, 2:27:46 PM11/11/15
to ISO C++ Standard - Future Proposals

I haven't updated the report yet, but I am more conversant with resumable functions (P0057) than I was 6 months ago.

Even so, I remain somewhat unclear on exactly how `await` would work with an expected type, or what the consequences are.

I get that the general idea is that `expected` would have the various awaitable machinery that `await <expr>` requires. So `operator await` can be called on `expected` types. And the resulting temporary object will have `await_ready`, `await_suspend`, and `await_resume`.

That's where I start to get foggy as to how this helps. As it has been described to me, the idea with `await <expected>` is that, if the expected object has an error, it will return from the current function and propagate the error to its caller.

It seems like this happens by having `await_ready` return false, which means that `await_suspend` will be called (presumably storing the error code somewhere), followed by hitting the `suspend-resume-point`. That will return control to the caller, and I'll assume that various resumable function machinery will cause the coroutine return value to pick up the error code via some mechanism.

My question is this: what happens to the current function? After all, the current function is not yet destroyed; it is only suspended. When does its call stack get cleaned up? Is that something that `await_suspend` does, even though we're about to hit the `suspend-resume-point`? Does it happen before the calling function gets to continue its execution?

I haven't seen an actual implementation of the various machinery (promise type & awaitable), so it's hard for me to understand the details of how this is supposed to work.

Another issue is the question of what this makes the function become. By using `await` in this way, the function becomes a coroutine. And that seems to create a degree of baggage. It may lead to an unwanted memory allocation (I'm still not sure when it gets destroyed, but however it happens, I suspect that this would be easy for compilers to optimize out).

It also seems to lead to some unavoidable stack overhead: the creation of an internal `promise_type` object. There may also be a bit of added overhead when returning a value, as that has to go through the promise object which assembles the return value. And there's nothing you can do about it even if you never get an error, unlike exceptions which can be implemented in a zero-overhead way until one gets thrown.

One other thing. With all of this coroutine machinery in place, the promise type and return value, would it be possible for such a coroutine to `await` on things that aren't `expected` values? That is, can you have a coroutine that `await`s on a `future`, then `await`s on an `expected`? I guess not, since a function can only return one kind of value, and the behavior of the coroutine for the caller is based entirely on that return type.

Or can you add special machinery to `future` and/or `generator` that can handle a function that `await`s on an `expected`?

I just don't know enough about the issues here, so I can't effectively assess them.

Vicente J. Botet Escriba

unread,
Nov 11, 2015, 3:58:21 PM11/11/15
to std-pr...@isocpp.org
Right. This is as if we called a function that throw an exception and
the exception is propagated to the caller. This is the magic thing that
makes await useful for expected<T>. A single keyword used to report the
errors transparently :)
> It seems like this happens by having `await_ready` return false, which
> means that `await_suspend` will be called (presumably storing the error
> code somewhere), followed by hitting the `suspend-resume-point`. That will
> return control to the caller, and I'll assume that various resumable
> function machinery will cause the coroutine return value to pick up the
> error code via some mechanism.
I don' master the last proposal, but the expected mapping should not
suspend as the value is already ready.
>
> My question is this: what happens to the current function? After all, the
> current function is not yet *destroyed*; it is only suspended. When does
> its call stack get cleaned up? Is that something that `await_suspend` does,
> even though we're about to hit the `suspend-resume-point`? Does it happen
> before the calling function gets to continue its execution?
As I said the coroutine would not be suspended. We need the help of Gor
to show the exact mapping.
>
> I haven't seen an actual implementation of the various machinery (promise
> type & awaitable), so it's hard for me to understand the details of how
> this is supposed to work.
>
> Another issue is the question of what this makes the function become. By
> using `await` in this way, the function becomes a coroutine.
Yes, however I don't think we should call it a coroutine as it never
suspend. Anyway, the proposal call them coroutines.
> And that seems
> to create a degree of baggage. It may lead to an unwanted memory allocation
> (I'm still not sure when it gets destroyed, but however it happens, I
> suspect that this would be easy for compilers to optimize out).
I don't see the need to allocate any additional memory.
>
> It also seems to lead to some unavoidable stack overhead: the creation of
> an internal `promise_type` object. There may also be a bit of added
> overhead when returning a value, as that has to go through the promise
> object which assembles the return value. And there's nothing you can do
> about it even if you never get an error, unlike exceptions which can be
> implemented in a zero-overhead way until one gets thrown.
You know surely more than me. I believe that we can not discuss it in
more detail until we have a specific mapping.
>
> One other thing. With all of this coroutine machinery in place, the promise
> type and return value, would it be possible for such a coroutine to `await`
> on things that aren't `expected` values?
I don't think this is possible. The opposite should be possible, for a
function returning a future to await on an expected, but I don't know if
the current proposal allows it.
> That is, can you have a coroutine
> that `await`s on a `future`, then `await`s on an `expected`? I guess not,
> since a function can only return one kind of value, and the behavior of the
> coroutine for the caller is based entirely on that return type.
Right.
>
> Or can you add special machinery to `future` and/or `generator` that can
> handle a function that `await`s on an `expected`?
I will create another thread about the possible interactions between
future<T> and expected<T>. I would like that future<T> has a ready state
represented as expected<T>. Maybe this would open the way to some kind
of interaction.
>
> I just don't know enough about the issues here, so I can't effectively
> assess them.
>
Gor, do you have coroutine_traits specialization for expected<T>, so
that we can see if there is any overhead?

Vicente

Gor Nishanov

unread,
Nov 11, 2015, 5:05:02 PM11/11/15
to ISO C++ Standard - Future Proposals
Please see attached file.
Here are a usage example:

expected<int> ok() { return 42; }


expected<int> bad() { return{ error, 5 }; }


expected<int> f() {

  auto x = await ok();

  auto y = await ok();

  return x + y;

}


expected<int> g() {

  auto x = await ok(); // unpacks result of OK into x

  auto y = await bad(); // will propagate the error as the result of g()

  return x + y;

}


Coroutine promise for the function returning expected<T> is:

namespace std { namespace experimental {

  template <typename T, typename... Whatever>

  struct coroutine_traits<expected<T>, Whatever...> {

    struct promise_type {

      expected<T>* r;

      void return_value(T val) { r->val = val; r->err = 0; }

      void return_value(error_t, int err) { r->err = err; }

      expected<T> get_return_object(expected<T>* x) { r = x; return expected<T>{}; }

   };

  };

} }


Note, this works with private version of compiler with a few tweaks that are not part of P0057R0/R1.


Namely, I made initial_suspend/final_suspend optional. If coroutine_promise does not have those, coroutine is not meant to be suspended.
Second tweak, (not sure if I like it, I am still experimenting), if coroutine is not suspendable, I pass a pointer to a value being returned to a get_return_object, this allows to deal with values like expected that are not split into consuming part (future) and the promise. I can also implement it without changing the get_return_object at all, but, that will require clarification in wording that in non-suspending coroutines get_return_object is called after user authored body is completed, as opposed to before user authored body is entered as it is traditional coroutines.

Shahms King

unread,
Nov 11, 2015, 5:11:58 PM11/11/15
to ISO C++ Standard - Future Proposals
Without await_transform() how does this deal with awaiting on a suspending awaitable from within a non-suspending promise?  At least with the most recent wording I've seen, the above is unsafe as it allows:

expected<T, E> unsafe() {
  auto value = await another_expected();  // Fine.
  auto next = await returns_a_future();  // Uh-oh, we've returned to our caller without a value.
  return T{};
}

--Shahms



--

Gor Nishanov

unread,
Nov 11, 2015, 5:25:46 PM11/11/15
to ISO C++ Standard - Future Proposals
Yep. As soon as I add await_transform (probably later this week), you will be able to ban things like future<T> from coroutines returning expected<T>.
It will look like this:

namespace std { namespace experimental {

   template <typename T> struct is_suspendable : true_type {}; // awaiting on anything can result in suspension

}}


Primary template states that by default we consider everything suspendable. We will specialize for expected to say that awaitng won't result in suspension:

namespace std { namespace experimental {

   template <typename T> struct is_suspendable<expected<T>> : false_type {}; // but not on expected

}}


And you add the following await_transform to the promise:

template <typename Awaitable>

enable_if<is_suspendable<Awaitable>::value> await_transform(Awaitable const&) {

  static_assert(false, "expected<T> does not support awaiting on suspendable types");

}


Now you will get a nice compile time error if you, say, await on a future.

Nicol Bolas

unread,
Nov 11, 2015, 5:27:11 PM11/11/15
to ISO C++ Standard - Future Proposals
On Wednesday, November 11, 2015 at 5:05:02 PM UTC-5, Gor Nishanov wrote:
Please see attached file

I don't see an attachment in your post. But I do see the code you put into the post.

One thing it's missing is the awaiter type. Or if `expected` has the appropriate interface, I don't see how that gets implemented. I want to see what `await_ready`, `await_suspend` and `await_resume` do for an `expected`.

Here are a usage example:

expected<int> ok() { return 42; }


expected<int> bad() { return{ error, 5 }; }


expected<int> f() {

  auto x = await ok();

  auto y = await ok();

  return x + y;

}


expected<int> g() {

  auto x = await ok(); // unpacks result of OK into x

  auto y = await bad(); // will propagate the error as the result of g()

  return x + y;

}


Coroutine promise for the function returning expected<T> is:

namespace std { namespace experimental {

  template <typename T, typename... Whatever>

  struct coroutine_traits<expected<T>, Whatever...> {

    struct promise_type {

      expected<T>* r;

      void return_value(T val) { r->val = val; r->err = 0; }

      void return_value(error_t, int err) { r->err = err; }

      expected<T> get_return_object(expected<T>* x) { r = x; return expected<T>{}; }

   };

  };

} }


Note, this works with private version of compiler with a few tweaks that are not part of P0057R0/R1.


I'd call something like this a bit more than a "tweak". You're effectively defining a new kind of coroutine.

Shahms King

unread,
Nov 11, 2015, 5:39:06 PM11/11/15
to std-pr...@isocpp.org
That certainly was my hope when I saw the mention of await_transform() in the "future directions".  Although the extra type_traits machinery is awkward, I don't see any way of avoiding it without pushing the mechanism onto the awaitable via the type of the coroutine_handle.

--Shahms

Gor Nishanov

unread,
Nov 11, 2015, 5:48:10 PM11/11/15
to ISO C++ Standard - Future Proposals
My bad. See the attached file now. (await_transform is not hooked up yet, it is there as an illustration).
expected.cpp

Vicente J. Botet Escriba

unread,
Nov 11, 2015, 5:57:00 PM11/11/15
to std-pr...@isocpp.org
As goto and addressing the memory directly?

I believe the name of the baby could change in the future.

For me await is the Haskel do-notation but for C++ (which is much more complex).

The initial goal of await was to simplify complex .then() chaining for futures. But futures are monads and .then is something similar mbind.
While the generalization was done it was done based on the interface futures provide. Now we see that we can do it for any monadic type.

await is sure something more than do-notation, bit IMO it is not only useful for coroutines.

Vicente 

Nicol Bolas

unread,
Nov 11, 2015, 6:14:32 PM11/11/15
to ISO C++ Standard - Future Proposals
Then it shouldn't be spelled "await". If it's not at least potentially waiting on something, then it should be called something else. When you're starting to change the design of a generally complete feature just to cover someone's pet use case, that becomes problematic.
Message has been deleted
Message has been deleted
Message has been deleted

Nicol Bolas

unread,
Nov 11, 2015, 6:51:10 PM11/11/15
to ISO C++ Standard - Future Proposals

OK, let's take a look at await_suspend:

template <typename T, typename P>
void await_suspend(expected<T> & val, std::experimental::coroutine_handle<P> h) {
    h
.promise().return_value(error, val.error());
    h
.destroy();
}

That's... terrifying.

So the function destroys the coroutine_handle. Notably, the handle for the function that it's currently executing within. After all, until the suspend-resume-point is reached, we're still in the body of that function (though to be fair, we'll hit that immediately after executing that statement). Isn't that a bit dangerous?

Also, as I understand it, the coroutine_handle holds the function's stack. Isn't the promise object on the stack? So wouldn't destroying it automatically destroy every object on the stack, including the promise? So how does the caller get a return value?

Obviously, I don't have access to your specific implementation of resumable functions, so things may have changed. But it seems to me like somebody's talking to a dead object.

If you're going to have non-resumable coroutines (which sounds like an oxymoron, given your definition of "coroutine"), if you want to allow people to basically hijack promises and `await` to terminate the function without an explicit return, maybe there should be some kind of explicit support for that. A coroutine_trait like you mentioned. That way, the await_suspend function doesn't have to do that kind of dangerous handle destruction.
Message has been deleted
Message has been deleted

Nicol Bolas

unread,
Nov 12, 2015, 9:10:19 AM11/12/15
to ISO C++ Standard - Future Proposals
OK, let's see if I can reply to Gor's mail directly to the forum.

On Wed, Nov 11, 2015 at 7:46 PM, Gor Nishanov wrote:
For some reason, google group keeps deleting my messages :-) . Let's see if replying via e-mail help.

So the function destroys the coroutine_handle. Notably, the handle for the function that it's currently executing within. After all, until the suspend-resume-point is reached, we're still in the body of that function (though to be fair, we'll hit that immediately after executing that statement). Isn't that a bit dangerous?

Coroutine is considered suspended before the call to await_suspend, thus calling any resumption member function such as resume() or destroy() are legal from await_suspend. P0057R0 was vague about that. It was clarified in P0057R1 which should be out this week in post-Kona mailing.

Fair enough.
 
 
Also, as I understand it, the coroutine_handle holds the function's stack. Isn't the promise object on the stack? So wouldn't destroying it automatically destroy every object on the stack, including the promise? So how does the caller get a return value?

Due to RVO, return value is in the caller frame, it is not part of the coroutine frame.

But it's the promise that's responsible for setting its value.

coroutine_trait like you mentioned. That way, the await_suspend function doesn't have to do that kind of dangerous handle destruction.

Possibly. This code is a day old. Maybe there are better ways of doing it. I will be also curious to see what clang guys come up with as this scenario is of interest to them as well.

There is also the issue that you have to add a bunch of extra machinery to promise/future in order to be able to await on both an expected and a future. That is, if your function looks like this:

future<expected<T, E>> foo()
{
 
auto data = await long_process(); //Schedules execution for later.
 
auto val = await data.something(); //Retrieves an `expected` and propagates the error
 
return val; //Should initialize the `T` part of the `expected`.
}

Vicente J. Botet Escriba

unread,
Nov 12, 2015, 2:08:59 PM11/12/15
to std-pr...@isocpp.org
Who want to do that?

Vicente

Nicol Bolas

unread,
Nov 12, 2015, 3:03:00 PM11/12/15
to ISO C++ Standard - Future Proposals

Someone who's working in code where exceptions are forbidden, yet wants to do asynchronous processing.

Vicente J. Botet Escriba

unread,
Nov 12, 2015, 5:05:10 PM11/12/15
to std-pr...@isocpp.org
If I was one of them I will define my own class future<T, error_code> and use expected<T, error_code> as storage.


BTW,

  auto val = await data.something();

Retrieves an `expected` and doesn't works, as if there is an error and data.something() returns expected<T,E>, we would need as result future<T,E>.

Note that decltype(val) would be T, not expected<T,E>.


Vicente
Reply all
Reply to author
Forward
0 new messages