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();
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.
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?
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.
>>> 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.
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?
- 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.
- 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.
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?
auto v2 = e2; if (!v2) return unexpected(v2.error());
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());
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.
I wonder if we add such a language facillity, we could turn the Coroutine TS into a pure library extension?
> 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.
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.
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.)
Niall
-- Bengt Gustafsson CEO, Beamways AB Westmansgatan 37 582 16 Linköping, Sweden +46 (705) 338259 Skype: benke_g www.beamways.com
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?
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.
#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();
})
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.
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.
(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.
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)
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.
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. Giventry {throw 0;} catch (int) {f();}it is not correct to optimize this intof();because f() might try to rethrow.
Easier to just say that if actually handling thrown exceptions turns out to be performance critical, then [...]
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).
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.
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.
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.
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?
Niall