D0779R0: Proposing operator try() draft 1 paper

321 views
Skip to first unread message

Niall Douglas

unread,
Oct 3, 2017, 6:03:20 PM10/3/17
to ISO C++ Standard - Future Proposals
I cannot stress strongly enough how unready this paper is for anyone else to see it. But seeing as Vicente asked in the thread about monadic operators, here it is.

Feedback is welcome, but be aware that Vicente already sent me a long list of things to change and fix. I have yet to even reply to his list of stuff. And Vicente also intends to submit an alternative proposal which is much more comprehensive than this paper.

This proposal paper however, I wish to keep short, simple and sweet, and I will do so and let Vicente's paper do the big picture stuff.

Niall

wg21_try_operator_20171003.pdf

Jens Maurer

unread,
Oct 4, 2017, 8:30:31 AM10/4/17
to std-pr...@isocpp.org
On 10/04/2017 12:03 AM, Niall Douglas wrote:
> I cannot stress strongly enough how unready this paper is for anyone else to see it. But seeing as Vicente asked in the thread about monadic operators, here it is.
>
> Feedback is welcome, but be aware that Vicente already sent me a long list of things to change and fix. I have yet to even reply to his list of stuff. And Vicente also intends to submit an alternative proposal which is much more comprehensive than this paper.

You're proposing a core language feature. That needs to go to EWG.

Please show the modified grammar; you mention a "cast-expression" that is
nowhere introduced.

Jens

Ville Voutilainen

unread,
Oct 4, 2017, 10:01:54 AM10/4/17
to ISO C++ Standard - Future Proposals
On 4 October 2017 at 15:30, Jens Maurer <Jens....@gmx.net> wrote:
> On 10/04/2017 12:03 AM, Niall Douglas wrote:
>> I cannot stress strongly enough how unready this paper is for anyone else to see it. But seeing as Vicente asked in the thread about monadic operators, here it is.
>>
>> Feedback is welcome, but be aware that Vicente already sent me a long list of things to change and fix. I have yet to even reply to his list of stuff. And Vicente also intends to submit an alternative proposal which is much more comprehensive than this paper.
>
> You're proposing a core language feature. That needs to go to EWG.

Indeed.

> Please show the modified grammar; you mention a "cast-expression" that is
> nowhere introduced.

Before we even go there, the paper needs to explain

a) why this is a problem that needs to be solved
b) why it needs a language extension

We can all see that it makes some code somewhere easier to write, and
we can grok that there are other
languages that have such a language facility. That alone doesn't
motivate a C++ language extension.
Furthermore,

c) why just an operator try and no other optional chaining facilities
like swift has?

Nicol Bolas

unread,
Oct 4, 2017, 10:48:00 AM10/4/17
to ISO C++ Standard - Future Proposals
Why it has to be a a language extension is easy: it's impossible to do it otherwise.

If you try to introduce a function that does what `try` does, it would be unable to do the one thing that makes `try` work: return. A function cannot force the calling function to return. You could make a macro that does what `try` does, but we have no way to do that within the language.

This is also part of why `co_await` has to be a language feature. You can't do what it does through the library. Or at least, not without a lot of pain.

Ville Voutilainen

unread,
Oct 4, 2017, 10:54:36 AM10/4/17
to ISO C++ Standard - Future Proposals
On 4 October 2017 at 17:48, Nicol Bolas <jmck...@gmail.com> wrote:
>> Before we even go there, the paper needs to explain
>>
>> a) why this is a problem that needs to be solved
>> b) why it needs a language extension
>>
>> We can all see that it makes some code somewhere easier to write, and
>> we can grok that there are other
>> languages that have such a language facility. That alone doesn't
>> motivate a C++ language extension.
>
>
> Why it has to be a a language extension is easy: it's impossible to do it
> otherwise.

That's great, but that explanation needs to be in the paper regardless of how
easy it supposedly is.

> If you try to introduce a function that does what `try` does, it would be
> unable to do the one thing that makes `try` work: return. A function cannot
> force the calling function to return. You could make a macro that does what
> `try` does, but we have no way to do that within the language.

Yes, well, perhaps we should look at adding something more general
than individual
keywords that perform specific transformations that then return (or
not) via a nettle bush
of traits and hooks.

Vicente J. Botet Escriba

unread,
Oct 4, 2017, 4:11:31 PM10/4/17
to std-pr...@isocpp.org, Ville Voutilainen
Le 04/10/2017 à 16:54, Ville Voutilainen a écrit :
> On 4 October 2017 at 17:48, Nicol Bolas <jmck...@gmail.com> wrote:
>>> Before we even go there, the paper needs to explain
>>>
>>> a) why this is a problem that needs to be solved
When we want to propagate the failure or get the value the current code
is something like

    auto res = expr();
    if (! res) return unexpected(res.error());  // idealy this should
be using the removed get_unexpected.


When you have a lot of them nested in a expression , you need to extract
the rerun logic outside the expression, e.g. if you need to add the
result of two calculations

    auto res1 = expr1();
    if (! res1) return unexpected(res1.error());

    auto res2 = expr2();
    if (! res2) return unexpected(res2.error());

    return *res1+ * res2


Using the try expression we can just do

    return try expr1()+ try expr2();


Clearly the last is much more readable and express better the intent
than the more elaborated code above.

LESS CODE MORE SOFTWARE
KISS

Ville, is this the kind of motivation you are asking for?

>>> b) why it needs a language extension
>>>
>>> We can all see that it makes some code somewhere easier to write, and
>>> we can grok that there are other
>>> languages that have such a language facility. That alone doesn't
>>> motivate a C++ language extension.
>>
>> Why it has to be a a language extension is easy: it's impossible to do it
>> otherwise.
> That's great, but that explanation needs to be in the paper regardless of how
> easy it supposedly is.
>
>> If you try to introduce a function that does what `try` does, it would be
>> unable to do the one thing that makes `try` work: return. A function cannot
>> force the calling function to return. You could make a macro that does what
>> `try` does, but we have no way to do that within the language.
> Yes, well, perhaps we should look at adding something more general
> than individual
> keywords that perform specific transformations that then return (or
> not) via a nettle bush
> of traits and hooks.
>

Yes, maybe there is something generalizing co_await and try. I don't
know. For me we need to go from concrete needs to more generic ones.

Do you have an idea of what this generic thing could be?

Vicente


Vicente

Hyman Rosen

unread,
Oct 4, 2017, 4:44:57 PM10/4/17
to std-pr...@isocpp.org, Ville Voutilainen
On Wed, Oct 4, 2017 at 4:11 PM, Vicente J. Botet Escriba <vicent...@wanadoo.fr> wrote:
    auto res1 = expr1(); if (! res1) return unexpected(res1.error());
    auto res2 = expr2(); if (! res2) return unexpected(res2.error());
    return *res1+ * res2;
Using the try expression we can just do
    return try expr1()+ try expr2();

Does that mean that strict left-to-right ordering is part of the proposal?
Otherwise, expr2() could be attempted and failed first, correct?

It seems to me that the "lambda parameters" that I've occasionally mentioned
here would subsume a lot of these special cases:
    template <typename T>
    operator+([] can_fail<T> e1, [] can_fail<T> e2) -> auto
    {
        auto v1 = e1; if (!v1) return unexpected(v1.error());
        auto v2 = e2; if (!v2) return unexpected(v2.error());
        return *v1 + *v2;
    }
    return expr1() + expr2();

The evaluation order is then up to you, and it's all implemented via a general purpose
language feature that's useful in many other contexts rather than being tied to one specific
special case.

Ville Voutilainen

unread,
Oct 4, 2017, 4:55:11 PM10/4/17
to Vicente J. Botet Escriba, ISO C++ Standard - Future Proposals
On 4 October 2017 at 23:11, Vicente J. Botet Escriba
No. The paper needs to explain why such code is so common that it
needs a core language facility
to help it.


>>> If you try to introduce a function that does what `try` does, it would be
>>> unable to do the one thing that makes `try` work: return. A function
>>> cannot
>>> force the calling function to return. You could make a macro that does
>>> what
>>> `try` does, but we have no way to do that within the language.
>>
>> Yes, well, perhaps we should look at adding something more general
>> than individual
>> keywords that perform specific transformations that then return (or
>> not) via a nettle bush
>> of traits and hooks.
>>
>
> Yes, maybe there is something generalizing co_await and try. I don't know.
> For me we need to go from concrete needs to more generic ones.
>
> Do you have an idea of what this generic thing could be?

Well, we could look at providing code transformation facilities
powerful enough that you
can provide what you need via a specific transformation. As a more limited idea,
there seems to be an occasional need to write operations that can exit
the surrounding
scope via break/continue/return, but functions can't do that, so
perhaps we could add
a block facility that can.

Niall Douglas

unread,
Oct 4, 2017, 5:46:51 PM10/4/17
to ISO C++ Standard - Future Proposals

You're proposing a core language feature.  That needs to go to EWG.

Good point.
 

Please show the modified grammar; you mention a "cast-expression" that is
nowhere introduced.

Yeah, I literally cannot make head nor tails of standardese. Might as well be Russian to me.

I did mention that the paper you've seen was a dump of a very early draft. I never would have posted it here without Vicente asking. Now I'm done with the Expected critique paper, I'll turn my full attention to the operator try paper. I'll be scanning the C++ standard and copying and pasting the standardese for operator new and trying to mash it up with the current standardese which I copy and pasted from the Coroutines TS.

So please don't have high hopes that the next revision I post here will be any better, but I will try my best.

Niall

Niall Douglas

unread,
Oct 4, 2017, 5:55:00 PM10/4/17
to ISO C++ Standard - Future Proposals

Before we even go there, the paper needs to explain

a) why this is a problem that needs to be solved
b) why it needs a language extension

We can all see that it makes some code somewhere easier to write, and
we can grok that there are other
languages that have such a language facility. That alone doesn't
motivate a C++ language extension.

Dunno, that seems a pretty great motivator to me. It saves boilerplate, clarifies code, and closely replicates standard practice in at least two other systems languages. Not sure what more you would want there.
 
Furthermore,

c) why just an operator try and no other optional chaining facilities
like swift has?

Purely because I want this proposal paper as simple and as short as possible. For example, I won't be proposing member function operator try either even though that makes a lot of sense too.

Remember I'm not on WG21, this paper is for others who are able to regularly attend to take up and champion. I'm just getting the ball rolling because Vicente is busy and I'm currently unemployed and have the time to put something on this together.

Niall

Ville Voutilainen

unread,
Oct 4, 2017, 5:55:10 PM10/4/17
to ISO C++ Standard - Future Proposals
On 5 October 2017 at 00:46, Niall Douglas <nialldo...@gmail.com> wrote:
>> Please show the modified grammar; you mention a "cast-expression" that is
>> nowhere introduced.
>>
> Yeah, I literally cannot make head nor tails of standardese. Might as well
> be Russian to me.
>
> I did mention that the paper you've seen was a dump of a very early draft. I
> never would have posted it here without Vicente asking. Now I'm done with
> the Expected critique paper, I'll turn my full attention to the operator try
> paper. I'll be scanning the C++ standard and copying and pasting the
> standardese for operator new and trying to mash it up with the current
> standardese which I copy and pasted from the Coroutines TS.
>
> So please don't have high hopes that the next revision I post here will be
> any better, but I will try my best.


I recommend focusing on the motivation/rationale and design
alternatives before diving head first
into wording.

Bengt Gustafsson

unread,
Oct 4, 2017, 5:58:50 PM10/4/17
to ISO C++ Standard - Future Proposals, vicent...@wanadoo.fr


Well, we could look at providing code transformation facilities
powerful enough that you
can provide what you need via a specific transformation. As a more limited idea,
there seems to be an occasional need to write operations that can exit
the surrounding
scope via break/continue/return, but functions can't do that, so
perhaps we could add
a block facility that can.
Unfortunately we don't know in the function originally producing the error how many levels of functions to exit.

Let me remind you all that we have a feature that does what you desire without any new keywords or chaining of operations. It is called exceptions. To me it seems that the longer this debate runs the closer we get to exception throwing/catching. I know exception unwinding mechanisms have a bad reputation for both performance and code size, but who has really done the measurements to prove this? Notably:

- We know that exception throwing/catching can have very small performance impact when no throwing occurs. But to be noted here is that each test of the optional/expected status involves some code that has to run every time and which includes a conditional jump, which can have a high performance impact. Hiding this behind a try operator or a bind() function may hide some gorier details but does not affect the generated code.

- The unwinding code that has to be generated for exception throwing consumes code bytes but do we know that this is necessarily or even typically more bytes than the code to test and dispatch on return values (regardless if this is explicit or generated code)? What is the impact of not having to load this code into the I-cache unless an exception actually occurs?

- Unless the program is done very carefully with nothrow on each level we will end up with both having unwind and if-based error handling code which will definitely be both larger and slower than today. As many common libraries are designed without nothrow declarations (regardless of if they actually throw or not) enforcing a no throwing policy may be very costly in terms of limitations of what libraries you may use.

I agree that error handling is boring and tedious to implement, but I don't see that expected helps that much compared to try blocks. So to make the case for any new mechanisms I think the performance/code size advantages must be substantial and investigated by testing on several compilers and platforms.

This said, I think that both std::optional and std::expected hare useful as they make it less tempting to encode error conditions in -1 or nullptr returns.

An alternative approach would be to continue on the path started with structured bindings and make it easier to specify to return a value + error code/flag pair, for instance by allowing anonymous struct as return type. While in theory there are some bytes to be saved by "unionify" the error code and the value the alignment requirements of the bool selector will usually make the expected object as large as the pair for typical error code sizes.

BTW: Will this be allowed:

if (auto a = f()) {
    if (auto c = g(a)) {
       ...
}}

I know you can use this style to test for nullptr pointer returns but does in generalize to any type with an operator bool()? If so this could be decent enough solution, maybe?

Nicol Bolas

unread,
Oct 4, 2017, 6:23:16 PM10/4/17
to ISO C++ Standard - Future Proposals, vicent...@wanadoo.fr
I disagree. It doesn't have to be "so common" currently. It does however have to explain why we should want to write such code, and explain why such idioms would become common if they were easy to write.
 
>>> If you try to introduce a function that does what `try` does, it would be
>>> unable to do the one thing that makes `try` work: return. A function
>>> cannot
>>> force the calling function to return. You could make a macro that does
>>> what
>>> `try` does, but we have no way to do that within the language.
>>
>> Yes, well, perhaps we should look at adding something more general
>> than individual
>> keywords that perform specific transformations that then return (or
>> not) via a nettle bush
>> of traits and hooks.
>>
>
> Yes, maybe there is something generalizing co_await and try. I don't know.
> For me we need to go from concrete needs to more generic ones.
>
> Do you have an idea of what this generic thing could be?

Well, we could look at providing code transformation facilities
powerful enough that you
can provide what you need via a specific transformation. As a more limited idea,
there seems to be an occasional need to write operations that can exit
the surrounding
scope via break/continue/return, but functions can't do that, so
perhaps we could add
a block facility that can.

See, the problem with that kind of thinking is that it leads down a never ending rabbit hole, where no problem ever gets solved because the solution isn't generic enough, thus forcing everyone to spend lots of fruitless time trying to get a solution for 1% of the stuff they want, even though the proposed one covers 99% of what they want.

Or to put in simpler language, the committee should not use the possibility of Reflection/Metaclasses/etc as an excuse to not improve the language. The possibility of such tools should not be used as an excuse for inaction on real problems.

Now, whether this is a real problem is a matter of debate. But you shouldn't disregard a tool just because it's not as generic as you might like. Such reasoning could easily be used to say that we shouldn't have Concepts, because it's not as generic as it could be.
 

Niall Douglas

unread,
Oct 4, 2017, 6:26:21 PM10/4/17
to ISO C++ Standard - Future Proposals, vicent...@wanadoo.fr

Let me remind you all that we have a feature that does what you desire without any new keywords or chaining of operations. It is called exceptions. To me it seems that the longer this debate runs the closer we get to exception throwing/catching. I know exception unwinding mechanisms have a bad reputation for both performance and code size, but who has really done the measurements to prove this?

Actually a lot of people have. SG14 invested lots of effort into this, and found no statistically measurable difference if failure was not common.

However iff failure is anything but rare, then exception throws on table based handling systems are expensive, about 2000 CPU cycles per frame unwound minimum. Table searching is pipeline unfriendly.
 

- We know that exception throwing/catching can have very small performance impact when no throwing occurs. But to be noted here is that each test of the optional/expected status involves some code that has to run every time and which includes a conditional jump, which can have a high performance impact. Hiding this behind a try operator or a bind() function may hide some gorier details but does not affect the generated code.

Expected/Outcome explicitly swaps worse constant runtime overhead in exchange for vastly improved failure execution times. Or, put another way, it's a library based implementation of pre-table exception handling so those who really need failure to be executed as fast as success have an alternative to the truly awful failure handling performance of table based exception handling.
 

- Unless the program is done very carefully with nothrow on each level we will end up with both having unwind and if-based error handling code which will definitely be both larger and slower than today. As many common libraries are designed without nothrow declarations (regardless of if they actually throw or not) enforcing a no throwing policy may be very costly in terms of limitations of what libraries you may use.

Modern compilers are superb at eliding exception handling within a translation unit. Just make sure all your extern functions are noexcept. The optimiser takes care of the rest within a translation unit.
 

I agree that error handling is boring and tedious to implement, but I don't see that expected helps that much compared to try blocks. So to make the case for any new mechanisms I think the performance/code size advantages must be substantial and investigated by testing on several compilers and platforms.

This was done during the Boost.Outcome peer review, and people came away impressed. You might also watch any of my conference videos on this topic e.g. the ACCU 2017 one.

It is not suitable for majority use cases. Nobody is claiming that this ought to replace exception throwing code. It is however very useful for fixed latency programming where you are very happy to exchange worse average performance for low 99% percentile latencies. SG14 is certainly keen on it, and for very good reason.

The AFIO library, which I hope to make into the C++ File I/O TS as a prelude to STL2 one day, is written using these objects and it has amazing performance. Think in terms of less than 100 nanoseconds for i/o at 99%, just 400 CPU cycles. That fast.

Niall

Ville Voutilainen

unread,
Oct 4, 2017, 6:36:09 PM10/4/17
to ISO C++ Standard - Future Proposals
On 5 October 2017 at 01:23, Nicol Bolas <jmck...@gmail.com> wrote:
>> > Do you have an idea of what this generic thing could be?
>>
>> Well, we could look at providing code transformation facilities
>> powerful enough that you
>> can provide what you need via a specific transformation. As a more limited
>> idea,
>> there seems to be an occasional need to write operations that can exit
>> the surrounding
>> scope via break/continue/return, but functions can't do that, so
>> perhaps we could add
>> a block facility that can.
>
>
> See, the problem with that kind of thinking is that it leads down a never
> ending rabbit hole, where no problem ever gets solved because the solution

This is why I shouldn't answer questions on this forum.

> isn't generic enough, thus forcing everyone to spend lots of fruitless time
> trying to get a solution for 1% of the stuff they want, even though the
> proposed one covers 99% of what they want.
>
> Or to put in simpler language, the committee should not use the possibility
> of Reflection/Metaclasses/etc as an excuse to not improve the language. The
> possibility of such tools should not be used as an excuse for inaction on
> real problems.
>
> Now, whether this is a real problem is a matter of debate. But you shouldn't
> disregard a tool just because it's not as generic as you might like. Such

I haven't disregarded anything. I have suggested what the paper needs
to explain in order to
have a chance of succeeding.

Vicente J. Botet Escriba

unread,
Oct 5, 2017, 2:04:31 AM10/5/17
to std-pr...@isocpp.org, Hyman Rosen, Ville Voutilainen
Le 04/10/2017 à 22:44, Hyman Rosen a écrit :
On Wed, Oct 4, 2017 at 4:11 PM, Vicente J. Botet Escriba <vicent...@wanadoo.fr> wrote:
    auto res1 = expr1(); if (! res1) return unexpected(res1.error());
    auto res2 = expr2(); if (! res2) return unexpected(res2.error());
    return *res1+ * res2;
Using the try expression we can just do
    return try expr1()+ try expr2();

Does that mean that strict left-to-right ordering is part of the proposal?
Otherwise, expr2() could be attempted and failed first, correct?
No in the previous case the order would be the same as if there were not try.


It seems to me that the "lambda parameters" that I've occasionally mentioned
here would subsume a lot of these special cases:
    template <typename T>
    operator+([] can_fail<T> e1, [] can_fail<T> e2) -> auto
    {
        auto v1 = e1; if (!v1) return unexpected(v1.error());
        auto v2 = e2; if (!v2) return unexpected(v2.error());
        return *v1 + *v2;
    }
    return expr1() + expr2();

I don't want to define this operator +and other handred of functions like this one. Defineing the operator once for a class is enough.

The evaluation order is then up to you, and it's all implemented via a general purpose
language feature that's useful in many other contexts rather than being tied to one specific
special case.
If I need to master the order then I would use several statements.

auto r2 = try expr2();
return  = try expr1() + r2;

Vicente

Jens Maurer

unread,
Oct 5, 2017, 3:12:17 AM10/5/17
to std-pr...@isocpp.org
On 10/05/2017 12:26 AM, Niall Douglas wrote:
>
> Let me remind you all that we have a feature that does what you desire without any new keywords or chaining of operations. It is called exceptions. To me it seems that the longer this debate runs the closer we get to exception throwing/catching. I know exception unwinding mechanisms have a bad reputation for both performance and code size, but who has really done the measurements to prove this?
>
>
> Actually a lot of people have. SG14 invested lots of effort into this, and found no statistically measurable difference if failure was not common.
>
> However iff failure is anything but rare, then exception throws on table based handling systems are expensive, about 2000 CPU cycles per frame unwound /minimum/. Table searching is pipeline unfriendly.

Could you please give a pointer to a specific paper(s) (URLs)?

> This was done during the Boost.Outcome peer review, and people came away impressed. You might also watch any of my conference videos on this topic e.g. the ACCU 2017 one.

Could you please give specific pointers (URLs)?

And please put those pointers in any upcoming WG21 paper, too.

Jens

Jens Maurer

unread,
Oct 5, 2017, 3:30:20 AM10/5/17
to std-pr...@isocpp.org
I fully agree with that; sorry for derailing the discussion by asking
for grammar. (However, it's still a puzzle to me how the new feature
actually fits in, e.g. regarding operator priorities, so I'd really
like to see at least a prose indication where in the expression grammar
this thing should go. Sample question:

try x + y

Is this "(try x) + y" or "try (x+y)"? And where in the proposal does
it say which one it is?)

Note 1: Once the actual feature is soundly baked, there will be people
available to help with standardese wording, myself included.

Note 2: In case anyone has not noticed, Ville is the chair of WG21's
Evolution Working Group and has seen a lot of proposals either succeed
or fail. Ignoring his advice makes it more likely that your proposal
faces the latter outcome.

Note 3: Features added to C++ can essentially never be undone, as
bad as they might be (cf. std::vector<bool>). So, WG21 needs *very*
good motivation why it should bear the maintenance burden of your
(any) desired feature for the next 20+ years to come. Have a look
at our ever-growing issues lists for an indication of "maintenance
burden".

Note 4: As a personal matter, I dislike coroutine's intense library
interdependencies, and the current proposal attempts to add more of
that.

Jens

Omer Rosler

unread,
Oct 5, 2017, 4:12:07 AM10/5/17
to ISO C++ Standard - Future Proposals

I wonder if we add such a language facillity, we could turn the Coroutine TS into a pure library extension?

Ville Voutilainen

unread,
Oct 5, 2017, 6:24:39 AM10/5/17
to ISO C++ Standard - Future Proposals
On 5 October 2017 at 11:12, Omer Rosler <omer....@gmail.com> wrote:
>> Yes, well, perhaps we should look at adding something more general
>> than individual
>> keywords that perform specific transformations that then return (or
>> not) via a nettle bush
>> of traits and hooks.
>
> I wonder if we add such a language facillity, we could turn the Coroutine TS into a pure library extension?

The transformation trigger would still need to be a language decision,
but the actual transformation might
be more library-ish, yes.

Niall Douglas

unread,
Oct 5, 2017, 9:08:27 AM10/5/17
to ISO C++ Standard - Future Proposals
> However iff failure is anything but rare, then exception throws on table based handling systems are expensive, about 2000 CPU cycles per frame unwound /minimum/. Table searching is pipeline unfriendly.

Could you please give a pointer to a specific paper(s) (URLs)?

I cannot. It would invite unhelpful bike shedding. Consider it a completely unsubstantiated claim from someone perhaps knowledgeable.

(I don't mean to brush off your request, but as soon as you cite benchmarks, people start inspecting the benchmark code, then they start bike shedding on "oh this is an unrealistic assumption" and "oh that wouldn't happen in real world code" and so on. Before you know it hundreds of emails have passed, and effort better expended elsewhere has been wasted. You should know I shared those results with the major compiler vendors and there was widespread surprise that it was that bad, but also no surprise that it would be that bad. One major compiler vendor confessed to me that the last time they had benchmarked exception throw and catch performance was a decade ago, and that in relative terms current CPU technology has made exception throw and catch relatively much worse than it used to be. The lesson here is that if failure occurs at any frequency and you care about bounded execution times, do not use exception throw and catch)
 

> This was done during the Boost.Outcome peer review, and people came away impressed. You might also watch any of my conference videos on this topic e.g. the ACCU 2017 one.

Could you please give specific pointers (URLs)?

https://www.youtube.com/watch?v=XVofgKH-uu4 is the ACCU talk. Note an errata is listed in the Youtube comments below the video.

The benchmarking of performance by people during the review I don't have, rather people went off, converted their code to use Outcome v1, benchmarked it and came back to say it was impressive.

I've got extensive benchmarking of AFIO on Intel's Optane storage devices, but I cannot release any of it due to lack of permission and a signed NDA. I can tell you it is impressive though. Expected/Outcome is a major win for that use case.
 

And please put those pointers in any upcoming WG21 paper, too.


I'm not entirely sure how this stuff is relevant to a proposed operator try?

Niall
 

Niall Douglas

unread,
Oct 5, 2017, 9:18:24 AM10/5/17
to ISO C++ Standard - Future Proposals

I fully agree with that; sorry for derailing the discussion by asking
for grammar.  (However, it's still a puzzle to me how the new feature
actually fits in, e.g. regarding operator priorities, so I'd really
like to see at least a prose indication where in the expression grammar
this thing should go.  Sample question:

  try x + y

Is this "(try x) + y"  or  "try (x+y)"?  And where in the proposal does
it say which one it is?)

If detailing that gets me out of writing standardese, I'm all in.

BTW where unspecified, I'll be proposing cloning whatever Rust and Swift do under the assumption that they've thought it through :)
 

Note 1: Once the actual feature is soundly baked, there will be people
available to help with standardese wording, myself included.

Superb. 

Note 2: In case anyone has not noticed, Ville is the chair of WG21's
Evolution Working Group and has seen a lot of proposals either succeed
or fail.  Ignoring his advice makes it more likely that your proposal
faces the latter outcome.

I am well aware. Ville probably won't remember it, but he helped me out with working through some technical issue or other quite a few years ago, perhaps more than five years ago now. I vaguely remember Hans Boehm was involved too. I remember him being quite patient in the face of my ignorance. I was and still am appreciative for the help at that time.

As I mentioned before, I'm only the primary lead on this proposal because I'm unemployed and Vicente is busy. I just want to get the ball rolling and let others carry it from there. I certainly cannot afford to regularly attend WG21 meetings. I'm also no language engineer, compilers and languages are not to my taste. I'm a libraries person, not actually bothered at all which programming language is used.
 

Note 3: Features added to C++ can essentially never be undone, as
bad as they might be (cf. std::vector<bool>).  So, WG21 needs *very*
good motivation why it should bear the maintenance burden of your
(any) desired feature for the next 20+ years to come.  Have a look
at our ever-growing issues lists for an indication of "maintenance
burden".

You may not be aware that I have a reputation as "deprecation strong". If I had the power, I'd axe half the Boost libraries as being undermaintained today. I'd also axe a good chunk of the C++ standard, I've already spoken at length with Bjarne and Gaby about it (they disagreed with my ideas in the strongest possible terms).
 

Note 4: As a personal matter, I dislike coroutine's intense library
interdependencies, and the current proposal attempts to add more of
that.

You mean this operator try proposal?

Can you explain what you mean by this? I don't know what you mean here at all.

Niall 

Matthew Woehlke

unread,
Oct 5, 2017, 10:57:34 AM10/5/17
to std-pr...@isocpp.org, Bengt Gustafsson, vicent...@wanadoo.fr
On 2017-10-04 17:58, Bengt Gustafsson wrote:
> I know exception unwinding mechanisms
> have a bad reputation for both performance and code size, but who has
> really done the measurements to prove this? Notably:
>
> - We know that exception throwing/catching can have very small performance
> impact when no throwing occurs. But to be noted here is that each test of
> the optional/expected status involves some code that has to run every time
> and which includes a conditional jump, which can have a high performance
> impact. Hiding this behind a try operator or a bind() function may hide
> some gorier details but does not affect the generated code.

Moreover, I would hope that the compiler is clever enough to optimize:

try {
...blah1...
if (something) throw specific_exception_t;
...blah2...
return ...;
} catch (specific_exception_t) {
...blah3...
}

...into:

...blah1...
if (something) goto caught_specific_exception_t;
...blah2...
return ...;
caught_specific_exception_t:
...blah3...

(...possibly also constructing a dummy exception if it can't prove it
doesn't need to do so.)

> BTW: Will this be allowed:
>
> if (auto a = f()) {
> if (auto c = g(a)) {
> ...
> }}

I would hope so! But worst case, you can write:

if (auto a = f(); a) ...

--
Matthew

Niall Douglas

unread,
Oct 5, 2017, 12:39:02 PM10/5/17
to ISO C++ Standard - Future Proposals, bengt.gu...@beamways.com, vicent...@wanadoo.fr

Moreover, I would hope that the compiler is clever enough to optimize:

  try {
    ...blah1...
    if (something) throw specific_exception_t;
    ...blah2...
    return ...;
  } catch (specific_exception_t) {
    ...blah3...
  }

...into:

  ...blah1...
  if (something) goto caught_specific_exception_t;
  ...blah2...
  return ...;
  caught_specific_exception_t:
  ...blah3...

(...possibly also constructing a dummy exception if it can't prove it
doesn't need to do so.)

The compiler is not able to make that optimisation. It is required to execute the full table search machinery at runtime for various reasons not important here. 

Niall

Vicente J. Botet Escriba

unread,
Oct 5, 2017, 1:21:11 PM10/5/17
to std-pr...@isocpp.org, Jens Maurer
operator try will have the same precedence as co_await ;-)

>
> Note 1: Once the actual feature is soundly baked, there will be people
> available to help with standardese wording, myself included.
>
> Note 2: In case anyone has not noticed, Ville is the chair of WG21's
> Evolution Working Group and has seen a lot of proposals either succeed
> or fail. Ignoring his advice makes it more likely that your proposal
> faces the latter outcome.
>
> Note 3: Features added to C++ can essentially never be undone, as
> bad as they might be (cf. std::vector<bool>). So, WG21 needs *very*
> good motivation why it should bear the maintenance burden of your
> (any) desired feature for the next 20+ years to come. Have a look
> at our ever-growing issues lists for an indication of "maintenance
> burden".
>
> Note 4: As a personal matter, I dislike coroutine's intense library
> interdependencies, and the current proposal attempts to add more of
> that.
The try-operator doesn't introduces any dependency on the standard
library. It depends on the requirements imposed to the result of the
operator try, but not on any C++ standard library file.

Vicente

Bengt Gustafsson

unread,
Oct 5, 2017, 5:09:23 PM10/5/17
to Niall Douglas, ISO C++ Standard - Future Proposals, vicent...@wanadoo.fr
Doesn't that depend on the ...blah... parts. If the compiler can deduce that they or whatever they call can't throw whatever the catch catches I can't see why not... It would be totally transparent. Well, it would need to check what the constructor of the exception type and its parents do also, of course. Maybe these requirements are viewed as too hard to do by compiler vendors?

Niall

-- 
Bengt Gustafsson
CEO, Beamways AB
Westmansgatan 37
582 16 Linköping, Sweden
+46 (705) 338259
Skype: benke_g
www.beamways.com

Jens Maurer

unread,
Oct 6, 2017, 5:46:43 AM10/6/17
to std-pr...@isocpp.org
On 10/05/2017 03:08 PM, Niall Douglas wrote:
> And please put those pointers in any upcoming WG21 paper, too.
>
>
> I'm not entirely sure how this stuff is relevant to a proposed operator try?

My view on WG21 is that, for at least some people, exceptions are still
the way to go for error propagation across multiple call frames, one
argument being that sprinkling your code with "if (error) return" litters
the CPU's instruction cache and branch predictor with almost-never-executed
stuff.

The proposed operator try, as far as I have understood, attempts to ease
the syntax burden of the "if (error) return" pattern, making it easier
to use that pattern, in balance discouraging exceptions for error
propagation. And people might object to the proposal on these grounds.

In my view, this proposal would be less of an uphill battle if
people can be convinced that there's a substantial (not fringe) set of
use cases for which exceptions aren't good enough; and part of that
means (in my view) showing performance figures for either approach,
including source code.

For example, when support for move operations was introduced into
the language, we had such figures for std::vector<std::string>
operations, showing the performance of an append or insert with and
without move. Everybody in the room could connect to the use
case (after all, std::vector<std::string> seems like the second
thing to try in C++).

By their very nature, exceptions and the "if (error) return" pattern
probably need somewhat less localized examples to make a real case
for the difference.

Jens





Niall Douglas

unread,
Oct 6, 2017, 11:01:53 AM10/6/17
to ISO C++ Standard - Future Proposals, nialldo...@gmail.com, vicent...@wanadoo.fr
 
Doesn't that depend on the ...blah... parts. If the compiler can deduce that they or whatever they call can't throw whatever the catch catches I can't see why not... It would be totally transparent. Well, it would need to check what the constructor of the exception type and its parents do also, of course. Maybe these requirements are viewed as too hard to do by compiler vendors?
No.

The compiler cannot ever elide a throw. It is an extern, non-inlineable function with unknowable side effects, same as a kernel syscall. The compiler cannot optimise it away except when it can deduce that it will never be called.

Niall

Niall Douglas

unread,
Oct 6, 2017, 11:44:32 AM10/6/17
to ISO C++ Standard - Future Proposals
On Friday, October 6, 2017 at 10:46:43 AM UTC+1, Jens Maurer wrote:
On 10/05/2017 03:08 PM, Niall Douglas wrote:
>     And please put those pointers in any upcoming WG21 paper, too.
>
>
> I'm not entirely sure how this stuff is relevant to a proposed operator try?

My view on WG21 is that, for at least some people, exceptions are still
the way to go for error propagation across multiple call frames, one
argument being that sprinkling your code with "if (error) return" litters
the CPU's instruction cache and branch predictor with almost-never-executed
stuff.

That would have been a reasonable opinion ten years ago. Modern branch predictors are far more intelligent. They spot things like if the CPU performs memory accesses X, Y and Z, then branch A not B will be taken. So in fact you can make every single line in your program wrapped in "if(error)" and you will probably not observe a measurable slowdown except in code being executed for the first time out of main memory. At least, on a Haswell or better Intel CPU.

Or, put another way, the old "Borland method" of stack frame based EH like Win32 still uses has become, relatively speaking, much more efficient than it used to be on recent CPUs. This has substantially narrowed the gap between stack frame based EH and table based EH. That is what made Swift and Rust choose the Expected form of error handling instead, and that is why we are bringing the same to C++ as a lighter weight alternative to exception throws for code which needs to be fixed latency.

But coming back to your point, I am well aware of the expert opinion against this change. I've been at persuading people for something like eighteen months now, Vicente for years longer again. I found that you can't convince people based on performance. Either they believe you (sometimes after doing some empirical testing on a recent CPU), or they don't. You can't change their preconceptions. Even on SG14, and they hate exceptions over there, there were quite a few who do not and will not accept a performance argument here.

So the more successful tack we've taken is to argue on the basis that C++ 03 era std::error_code use patterns are nasty, and these objects make using std::error_code much less nasty. Anyone who looks at the current std::error_code use patterns in the Filesystem TS or the Networking TS will almost certainly come away agreeing. So we've concentrated on that, and made good progress. The very positive rejection of Outcome v1 was a surprise in fact, I had expected refusal because-we-don't-like-this-around-these-parts.

Hence, we don't talk about performance much. It gets people onto their high horse, and that isn't productive to progressing these objects rapidly. Better to argue from reduction of nasty practice, not better performance.
 

The proposed operator try, as far as I have understood, attempts to ease
the syntax burden of the "if (error) return" pattern, making it easier
to use that pattern, in balance discouraging exceptions for error
propagation.  And people might object to the proposal on these grounds.

Well, the alternative is a C macro. Here is an identically behaving C macro which uses a GCC/clang only language extension:

#define OUTCOME_TRYX(m)  
 
({                                                                                                                
   
auto &&res = (m);                                                                                              
   
if(!res.has_value())                                                                                        
     
return OUTCOME_V2_NAMESPACE::try_operation_return_as(std::forward<decltype(res)>(res));                    
    std
::forward<decltype(res)>(res).value();                                                                
 
})


That's non-portable outside clang and GCC of course.

Perhaps people don't realise how frequently the TRY operation is used? Have a look at https://github.com/ned14/afio/blob/master/include/afio/v2.0/algorithm/shared_fs_mutex/memory_map.hpp#L188. Note all the OUTCOME_TRY macro calls. And note each of those function calls also do many TRY operations too. There might be as many as fifty TRY operations in just that object's constructor.
 

In my view, this proposal would be less of an uphill battle if
people can be convinced that there's a substantial (not fringe) set of
use cases for which exceptions aren't good enough; and part of that
means (in my view) showing performance figures for either approach,
including source code.

How long is a piece of string?

As mentioned before, performance based arguments just descend into unproductive, non-empirically based, personal claims and counter claims. A de-nastifying argument has worked better to date. 

That said, the de-nastifying argument is much easier with Outcome than Expected. Outcome's v2 design was specifically agreed upon to "save" us from the error_code overloads in Boost.Filesystem where, If Boost.Outcome is accepted, the implication was during the review that we'll be adding Outcome overloads for all the Boost.Filesystem error_code taking functions as an extension, and hinting to users to not use the error_code overloads.

Expected isn't as focused on solving a single problem as Outcome. It's a more abstract, less concrete argument as a result.

Niall

Nevin Liber

unread,
Oct 6, 2017, 12:06:02 PM10/6/17
to std-pr...@isocpp.org
On Fri, Oct 6, 2017 at 10:44 AM, Niall Douglas <nialldo...@gmail.com> wrote:
On Friday, October 6, 2017 at 10:46:43 AM UTC+1, Jens Maurer wrote:
My view on WG21 is that, for at least some people, exceptions are still
the way to go for error propagation across multiple call frames, one
argument being that sprinkling your code with "if (error) return" litters
the CPU's instruction cache and branch predictor with almost-never-executed
stuff.

But coming back to your point, I am well aware of the expert opinion against this change. I've been at persuading people for something like eighteen months now, Vicente for years longer again. I found that you can't convince people based on performance.

When people ask you for numbers and measurements, you say:

(I don't mean to brush off your request, but as soon as you cite benchmarks, people start inspecting the benchmark code, then they start bike shedding on "oh this is an unrealistic assumption" and "oh that wouldn't happen in real world code" and so on. Before you know it hundreds of emails have passed, and effort better expended elsewhere has been wasted. [...])

Given that you are not willing to provide evidence to back up your claims, your argument boils down to "trust me".  I vastly prefer "trust but verify", because in my experience, many people, a lot of which are real experts, have presented things and reasoned things out that turn out not to be valid.

It is much, much harder to convince people with unsubstantiated claims on performance than to convince them with substantiated claims about performance.
--
 Nevin ":-)" Liber  <mailto:ne...@eviloverlord.com>  +1-847-691-1404

Jens Maurer

unread,
Oct 6, 2017, 1:47:42 PM10/6/17
to std-pr...@isocpp.org
On 10/06/2017 05:01 PM, Niall Douglas wrote:
> The compiler cannot ever elide a throw. It is an extern, non-inlineable function with unknowable side effects, same as a kernel syscall. The compiler cannot optimise it away except when it can deduce that it will never be called.

This might be true for current compiler technology, but from
the point-of-view of the C++ standard, optimizations around
throw / catch are certainly possible. All side-effects (e.g.
copies of the exception object) are known or could be known
to the compiler.

Jens

Niall Douglas

unread,
Oct 6, 2017, 2:07:09 PM10/6/17
to ISO C++ Standard - Future Proposals

Given that you are not willing to provide evidence to back up your claims, your argument boils down to "trust me".  I vastly prefer "trust but verify", because in my experience, many people, a lot of which are real experts, have presented things and reasoned things out that turn out not to be valid.

That isn't the case in this situation. But I do agree that the strong performance use case motivation is only on high end hardware, in code where calling malloc is unacceptable. So SG14 leaning folk, not the majority on WG21.
 

It is much, much harder to convince people with unsubstantiated claims on performance than to convince them with substantiated claims about performance.

I agree. Hence my recommendation that the case be made on other points without referring at all to performance.

Niall 

Arthur O'Dwyer

unread,
Oct 6, 2017, 2:10:17 PM10/6/17
to ISO C++ Standard - Future Proposals
Jens's theory sounds solid to me. Niall, do you have some specific reason in mind why throw-catch pairs shouldn't be elided even by a sufficiently smart compiler? (The only thing I can think of is debuggability; but at -O3 that argument goes out the window.)

For the record: regardless of theoretical concerns, in practice no current compiler succeeded at optimizing away my throw-catch of a completely trivial "int".
https://godbolt.org/g/VyrhrB (throw int, catch int)
https://godbolt.org/g/V1UiUq  (throw int, catch dot-dot-dot)

Anyway, I know this is pretty off-topic for this thread. On the other hand, when I see code like https://github.com/ned14/afio/blob/master/include/afio/v2.0/algorithm/shared_fs_mutex/memory_map.hpp#L188 , my immediate kneejerk reaction is "This looks like a slam-dunk for exception handling." It's just littered with "if error, return" checks; and none of the errors look "likely" so we're not talking about using exceptions for control flow; and you've already got a catch-dot-dot-dot-return-error at the bottom of the function.
Being able to write this code in terms of "operator try" would definitely make it look cleaner. But if you use EH, then you get the same semantics and you don't even have to write the word "try" everywhere! It would just work. So there's got to be a compelling reason for you not to use EH; and that has to come down to performance, which is why your critics keep harping on the subject of performance. :)

The other popular reason not to use EH in a codebase is that EH makes it hard to reason about control-flow: you end up with control-flow edges that aren't reflected in the explicitly written code. It's like "goto" but worse (to people in that camp).  Unfortunately, "operator try" has exactly the same smell as EH does, in that respect.

HTH,
Arthur

Niall Douglas

unread,
Oct 6, 2017, 2:11:30 PM10/6/17
to ISO C++ Standard - Future Proposals
I didn't want to get into the reasons why it can't happen, but if you ask a compiler vendor, you'll quickly learn why.

For Windows, MSVC uses the kernel-wide SEH/TEH system. It does not permit elision. LLVM also provides a universal system to allow languages to interoperate. It does not permit elision.

The C++ standard does permit elision as far as my reading of it can make out. But I am unaware of any implementation which actually does so, and it's for lots of very good reasons for backwards compatibility, interop, and external tooling support.

In short, even if the standard allows it, I don't think you'll succeed in persuading any vendor to actually implement it. The cost benefit isn't there for them.

Niall

Richard Smith

unread,
Oct 6, 2017, 2:33:37 PM10/6/17
to std-pr...@isocpp.org
On 6 October 2017 at 11:10, Arthur O'Dwyer <arthur....@gmail.com> wrote:
On Friday, October 6, 2017 at 10:47:42 AM UTC-7, Jens Maurer wrote:
On 10/06/2017 05:01 PM, Niall Douglas wrote:
> The compiler cannot ever elide a throw. It is an extern, non-inlineable function with unknowable side effects, same as a kernel syscall. The compiler cannot optimise it away except when it can deduce that it will never be called.

This might be true for current compiler technology, but from
the point-of-view of the C++ standard, optimizations around
throw / catch are certainly possible.  All side-effects (e.g.
copies of the exception object) are known or could be known
to the compiler.

Jens's theory sounds solid to me. Niall, do you have some specific reason in mind why throw-catch pairs shouldn't be elided even by a sufficiently smart compiler? (The only thing I can think of is debuggability; but at -O3 that argument goes out the window.)

For the record: regardless of theoretical concerns, in practice no current compiler succeeded at optimizing away my throw-catch of a completely trivial "int".
https://godbolt.org/g/VyrhrB (throw int, catch int)
https://godbolt.org/g/V1UiUq  (throw int, catch dot-dot-dot)

Here's the big problem with that optimization: the catch is usually more complex than you're showing. Given

try {
  throw 0;
} catch (int) {
  f();
}

it is not correct to optimize this into

  f();

because f() might try to rethrow. You actually need to activate the exception allocation and registration mechanism *as if* an exception had been thrown and subsequently caught, and then deallocate the exception at the end of the catch block. Easier to just say that if actually handling thrown exceptions turns out to be performance criticial, then exceptions were a bad choice for that control flow.
 
Anyway, I know this is pretty off-topic for this thread. On the other hand, when I see code like https://github.com/ned14/afio/blob/master/include/afio/v2.0/algorithm/shared_fs_mutex/memory_map.hpp#L188 , my immediate kneejerk reaction is "This looks like a slam-dunk for exception handling." It's just littered with "if error, return" checks; and none of the errors look "likely" so we're not talking about using exceptions for control flow; and you've already got a catch-dot-dot-dot-return-error at the bottom of the function.
Being able to write this code in terms of "operator try" would definitely make it look cleaner. But if you use EH, then you get the same semantics and you don't even have to write the word "try" everywhere! It would just work. So there's got to be a compelling reason for you not to use EH; and that has to come down to performance, which is why your critics keep harping on the subject of performance. :)

I don't think that follows. The argument doesn't have to come down to performance, as you note:
 
The other popular reason not to use EH in a codebase is that EH makes it hard to reason about control-flow: you end up with control-flow edges that aren't reflected in the explicitly written code. It's like "goto" but worse (to people in that camp).  Unfortunately, "operator try" has exactly the same smell as EH does, in that respect.

"try" does not introduce invisible control flow. EH does. I would expect reading and reasoning about correctness of code using "try" to be much easier than doing the same for code using EH, because with "try" it is clear exactly which points within your function can return (and thus at which points you must satisfy your external invariants).

Ville Voutilainen

unread,
Oct 6, 2017, 2:38:52 PM10/6/17
to ISO C++ Standard - Future Proposals
For what it's worth, to me a type like expected/outcome is obviously
necessary, and the reason isn't performance.
I would use such a type to collect results from multiple worker
functions (possibly but not necessarily from multiple threads)
without having to deal with exceptions when I do so, because having to
deal with exceptions makes the collecting
code ugly as sin. That is,
a) I have a bag of operations to run
b) I launch those operations in different workers
c) I gather the results into a container and return that container to the caller

Being able to do that conveniently is reason alone to have an
expected/outcome type. The actual errors are dealt with by
the caller when it examines the container of results.

Being able to avoid return-via-parameter is a very good bonus, but to
me less major than the collection-gathering ability.

Being able to avoid exception overhead in a couple of use cases is a
good bonus, but a far lesser one to me than the top
one or the verygoodbonus one.

Jens Maurer

unread,
Oct 6, 2017, 4:16:37 PM10/6/17
to std-pr...@isocpp.org
On 10/06/2017 08:38 PM, Ville Voutilainen wrote:
> For what it's worth, to me a type like expected/outcome is obviously
> necessary,

Fully agreed.

> I would use such a type to collect results from multiple worker
> functions (possibly but not necessarily from multiple threads)
> without having to deal with exceptions when I do so, because having to
> deal with exceptions makes the collecting
> code ugly as sin. That is,
> a) I have a bag of operations to run
> b) I launch those operations in different workers
> c) I gather the results into a container and return that container to the caller

That sounds like a good use-case to show in a paper, with
exception vs. expected/outcome code compared.

Jens

Arthur O'Dwyer

unread,
Oct 6, 2017, 5:28:39 PM10/6/17
to ISO C++ Standard - Future Proposals
On Fri, Oct 6, 2017 at 11:33 AM, Richard Smith <ric...@metafoo.co.uk> wrote:
On 6 October 2017 at 11:10, Arthur O'Dwyer <arthur....@gmail.com> wrote:
On Friday, October 6, 2017 at 10:47:42 AM UTC-7, Jens Maurer wrote:
On 10/06/2017 05:01 PM, Niall Douglas wrote:
> The compiler cannot ever elide a throw.

This might be true for current compiler technology, but from
the point-of-view of the C++ standard, optimizations around
throw / catch are certainly possible.
Jens's theory sounds solid to me.
For the record: regardless of theoretical concerns, in practice no current compiler succeeded at optimizing away my throw-catch of a completely trivial "int".
https://godbolt.org/g/VyrhrB (throw int, catch int)
https://godbolt.org/g/V1UiUq  (throw int, catch dot-dot-dot)

Here's the big problem with that optimization: the catch is usually more complex than you're showing. Given

try {
  throw 0;
} catch (int) {
  f();
}

it is not correct to optimize this into

  f();

because f() might try to rethrow.

Sure, but in Niall's case the catch was (approximately) that simple.
I can think of another complexity that could disallow the optimization in some cases:

    try { RAIItype t; throw 0; } catch (int) {}

cannot generally be optimized into

    { RAIItype t; }

unless you can prove (by inlining) that RAIItype's destructor does not inspect uncaught_exceptions().
But in the case where the compiler can see all the side-effects that are happening (because inlining), I'd consider this a missed optimization opportunity.

 
Easier to just say that if actually handling thrown exceptions turns out to be performance critical, then [...]

Sure, real compilers probably omit this optimization because it gets no benefit on real code (and if it does give a benefit then the code is too weird to live and you should rewrite it — i.e., the same kind of virtuous cycle that leads to "good code doesn't use EH", "good code doesn't use dynamic_cast", etc.).
"We don't do it because the return on investment is too low" is a great answer. But it's philosophically a different answer from "we don't do it because we're not allowed to."


The other popular reason not to use EH in a codebase is that EH makes it hard to reason about control-flow: you end up with control-flow edges that aren't reflected in the explicitly written code. It's like "goto" but worse (to people in that camp).  Unfortunately, "operator try" has exactly the same smell as EH does, in that respect.

"try" does not introduce invisible control flow. EH does. I would expect reading and reasoning about correctness of code using "try" to be much easier than doing the same for code using EH, because with "try" it is clear exactly which points within your function can return (and thus at which points you must satisfy your external invariants).

Hm, I suppose so. I was thinking that "int foo = try bar()" was subtle enough that it counted as a non-explicit goto; but you're right that there's a fundamental difference between "try bar()" and just "bar()". The "try" gives the original author something physical to point to and tell the maintainer, "Look, right there! 'Try'! You didn't see that early return? It's right there in the code! 'Try'! It's not like the control flow edge was invisible..." ;)

–Arthur

Niall Douglas

unread,
Oct 7, 2017, 6:47:58 PM10/7/17
to ISO C++ Standard - Future Proposals

Jens's theory sounds solid to me. Niall, do you have some specific reason in mind why throw-catch pairs shouldn't be elided even by a sufficiently smart compiler? (The only thing I can think of is debuggability; but at -O3 that argument goes out the window.)

The C++ standard may allow throw elision, but the other (local, platform, proprietary etc) standards which a C++ compiler must also conform to do not. This certainly applies to MSVC and LLVM. Technically, I don't believe there is a requirement on GCC except one of cost-benefit and backwards compatibility. But don't quote me on that.

MSVC, incidentally, has the best performing throw...catch implementation currently. But then it's implemented by the Windows kernel, and there is somebody buried in Microsoft somewhere whose day job it is to make TEH execute quickly, not least that its performance affects SQL Server benchmarks because NTFS (used to?) use TEH extensively internally.
 

For the record: regardless of theoretical concerns, in practice no current compiler succeeded at optimizing away my throw-catch of a completely trivial "int".
https://godbolt.org/g/VyrhrB (throw int, catch int)
https://godbolt.org/g/V1UiUq  (throw int, catch dot-dot-dot)

Which is what I said earlier. I know it is important for others to check what I claim, but I did do my homework before I began any of this. Equally, my homework may have been true three years ago and not now, but so far so good.

BTW there is one case that I know of where throws are lifted out and that is GCC under LTO where you're linking an executable. It appears to fold trivial stack unwinds together, and then very curiously it'll loop the throw...catch cycle by however many stack unwinds were folded together. This ends up faster due to hot caching, but it's non-intuitive assembler to study (and my analysis was not deep, my conclusion on this may be wrong, LTOed code is hard to grok).
 

Anyway, I know this is pretty off-topic for this thread. On the other hand, when I see code like https://github.com/ned14/afio/blob/master/include/afio/v2.0/algorithm/shared_fs_mutex/memory_map.hpp#L188 , my immediate kneejerk reaction is "This looks like a slam-dunk for exception handling." It's just littered with "if error, return" checks; and none of the errors look "likely" so we're not talking about using exceptions for control flow; and you've already got a catch-dot-dot-dot-return-error at the bottom of the function.

:)

Firstly, the try-catch(...) is completely superfluous and will be elided entirely by the compiler. It's merely my personal style from bitter experience that when writing a function marked noexcept that has any complexity at all, I wrap it into an Outcome generating exception catch all because it does no harm, and guarantees that std::terminate will never be unintentionally called.

Secondly, you are missing the lack of need to implement exception safety in that routine. That routine is very carefully written to be race free and reentrant, and I find reasoning about the correct logic to be race free and reentrant to be easier when there is zero possibility of an exception throw.

Thirdly, that routine in fact experiences failure quite frequently, and thus exception throws would be a severe, and unbounded, performance hit. I have several unit tests which deliberately abuse that routine in fact to try and tease out races, and I suspect there is still one race in there because those tests occasionally trip on FreeBSD, but do not on any other system I have access to. I really ought to expend a week getting into it on a FreeBSD system. But other priorities, as always.

Finally, despite the hefty quantity of work that routine does, complete with iterating TLB shootdowns, it still has superb performance.  By "superb" I mean bounded, so if you plot a latency graph, there's only a ~50% latency bump at 99% over the median. That's really great given we're creating random files and mmapping them and byte range locking them in a loop. Latencies do go really awful after 99% though, but we are contending on a single file entry, it's an unavoidable mutex in the kernel.
 

The other popular reason not to use EH in a codebase is that EH makes it hard to reason about control-flow: you end up with control-flow edges that aren't reflected in the explicitly written code. It's like "goto" but worse (to people in that camp).  Unfortunately, "operator try" has exactly the same smell as EH does, in that respect.

Another really nice thing about Expected/Outcome using code is that it ought to able to be fed to open source formally verifying C++ compilers like certain unofficial forks of CompCert. I am unaware of an open source formally verifying C++ compiler which will permit exception throws as that's very considerably harder, and to my knowledge that remains a feature of proprietary tooling only.

(Note this is an untested claim, hence why I don't mention it normally. But in the pure theoretical sense, Expected/Outcome using code which never throws exceptions has a much more bounded set of potential control flow paths than exception throwing code, and thus formal verification of more realistically large programs in a reasonable time period ought to be more practical)

Niall

Niall Douglas

unread,
Oct 7, 2017, 6:56:47 PM10/7/17
to ISO C++ Standard - Future Proposals
I would use such a type to collect results from multiple worker
functions (possibly but not necessarily from multiple threads)
without having to deal with exceptions when I do so, because having to
deal with exceptions makes the collecting
code ugly as sin. That is,
a) I have a bag of operations to run
b) I launch those operations in different workers
c) I gather the results into a container and return that container to the caller

Being able to do that conveniently is reason alone to have an
expected/outcome type. The actual errors are dealt with by
the caller when it examines the container of results.

This was another topic which consumed extensive discussion during the Boost peer review of Outcome.

There was a faction who felt that accumulating results like you just described was important (I was in that faction), and others who felt that that use case was not so important relative to easy returns from functions and a less fussy, less flexible design. Due to me being in the "accumulator faction" I had designed v1 Outcome to have a formal empty state specifically for accumulation, helped by the fact that implementation of a formal empty state is zero cost in a union-based implementation.

This proved to be a highly contentious design decision, and I was eventually persuaded that if the programmer means to accumulate into one of these objects, they ought to write optional<result<Foo>>, and make result<Foo> incapable of either default construction or being ever empty to force people to write optional<> when they mean optional<>. 

Still sceptical, I tried the optional-based approach in my existing Outcome using libraries and found that it did increase readability and auditability. It thus became evident that my original position had been wrong, and the other faction had been right.

Thus Outcome v2 takes this approach of disabling default construction and any possibility of empty state, and the tutorial will lay it on thick that when accumulating results, annotate with "optional<>".

Niall

Giovanni Piero Deretta

unread,
Oct 12, 2017, 7:05:07 AM10/12/17
to ISO C++ Standard - Future Proposals, ville.vo...@gmail.com
On Wednesday, October 4, 2017 at 9:11:31 PM UTC+1, Vicente J. Botet Escriba wrote:
Le 04/10/2017 à 16:54, Ville Voutilainen a écrit :
> On 4 October 2017 at 17:48, Nicol Bolas <jmck...@gmail.com> wrote:
>>> Before we even go there, the paper needs to explain
>>>
>>> a) why this is a problem that needs to be solved
When we want to propagate the failure or get the value the current code
is something like

     auto res = expr();
     if (! res) return unexpected(res.error());  // idealy this should
be using the removed get_unexpected.


When you have a lot of them nested in a expression , you need to extract
the rerun logic outside the expression, e.g. if you need to add the
result of two calculations

     auto res1 = expr1();
     if (! res1) return unexpected(res1.error());

     auto res2 = expr2();
     if (! res2) return unexpected(res2.error());

     return *res1+ * res2


Using the try expression we can just do

     return try expr1()+ try expr2();


Clearly the last is much more readable and express better the intent
than the more elaborated code above.

LESS CODE MORE SOFTWARE
KISS

Ville, is this the kind of motivation you are asking for?

>>> b) why it needs a language extension
>>>
>>> We can all see that it makes some code somewhere easier to write, and
>>> we can grok that there are other
>>> languages that have such a language facility. That alone doesn't
>>> motivate a C++ language extension.
>>
>> Why it has to be a a language extension is easy: it's impossible to do it
>> otherwise.
> That's great, but that explanation needs to be in the paper regardless of how
> easy it supposedly is.
>
>> If you try to introduce a function that does what `try` does, it would be
>> unable to do the one thing that makes `try` work: return. A function cannot
>> force the calling function to return. You could make a macro that does what
>> `try` does, but we have no way to do that within the language.
> Yes, well, perhaps we should look at adding something more general
> than individual
> keywords that perform specific transformations that then return (or
> not) via a nettle bush
> of traits and hooks.
>

Yes, maybe there is something generalizing co_await and try. I don't
know. For me we need to go from concrete needs to more generic ones.

Do you have an idea of what this generic thing could be?


first class continuations.

Giovanni Piero Deretta

unread,
Oct 12, 2017, 7:27:29 AM10/12/17
to ISO C++ Standard - Future Proposals, nialldo...@gmail.com, vicent...@wanadoo.fr

Of course it can, its behavior is described in the standard. It is not more unknowable than printf, malloc or memcpy, which are all routinely optimized. Compilers don't bother because the optimization is non trivial due to the non local jump and existing codebases would benefit very little from it.
 
Niall
Reply all
Reply to author
Forward
0 new messages