Date: Fri, 07 Apr 2023 21:54:59 -0300
On Friday, 7 April 2023 20:53:56 -03 Andrey Semashev via Std-Discussion wrote:
> Sure, the standard may simply require a certain behavior, and I did not
> intend my proposed solution to be adopted or even be part of the
> standard wording. I simply indicated that there is a way to achieve
> this, which is something that is welcome for any proposal, isn't it?
Oh, sure.
> > For most cases, this can be implemented already, with the ability to
> > deactivate a scope, because the typical use of such guards is to have them
> > execute on early returns. At the moment when we've achieved success, it
> > can be then dismissed -- compiler optimisations detecting such conditions
> > would be welcome too.
>
> This would be error-prone if there are multiple successful (or failure)
> return points. Scope guards are specifically aimed to relieve you from
> having to worry about missing the action upon leaving the scope, and
> having to manually (de)activate the guard before returning goes against
> this goal.
I disagree that it is error-prone. So long as all the early returns (including
throws) are of the same category -- either success or failure -- then you
simply keep the guard active, until the point when you've assured success. At
that point, you simply dismiss it.
I agree that it's not a full solution. I'm simply saying that I expect this to
be a sufficient solution for 90% of the cases. Though, admittedly, I have no
clue about co-routines and suspended states, so I may be missing something
important there.
> > For those scenarios, why can't the user-provided lambda that is executed
> > decide whether to do anything at all? Why do we need to give the scope
> > guard object two lambdas: one to decide whether to run and the other to
> > run?
> Yes, scope_success/scope_fail can be implemented in terms of scope_exit,
> but their benefit is reducing code duplication. This is most apparent
> when the failure condition is the thrown exception - you simply don't
> have to write the code for testing whether an exception is in flight.
Agreed on detecting exceptions as a failure. See below.
> This is also the case for other common cases, like testing whether an
> error code indicates error. The condition function need not be a lambda,
> it can be a function object that is written once and then reused in
> multiple scope guards.
Such an object can simply be captured and queried inside the lambda of the
guard action.
I understand the convenience value, but I am questioning whether it's worth
the cost for implementers as well as the cognitive load for users: will they
remember to use this variant instead of the generic one?
I think if you want to argue on the need for this, then it needs to carry a
reasonable example that couldn't be simply rewritten with an extra "if" inside
the lambda.
> There is also value in code readability. Seeing scope_fail will
> immediately give you the idea that this is an error handler rather than
> a general cleanup.
I agree scope_fail is useful for the "is exception active" detection.
I am guessing you're arguing that, as scope_fail may already be in use in some
contexts due to its ability to detect when an exception is active, it might be
useful to allow the user to extend it to add more failure conditions.
What I was thinking was on the case that the user was trying to use scope_fail
without exceptions active: I don't see the point. They may as well use a plain
scope_guard and have their error condition checked inside the lambda. Or, as
argued above, not do any checking and dismiss when success has been assured.
> > Can this simply be QoI? The control variable of a dismissable scope guard
> > that isn't dismissed can itself be garbage-collected after optimisation
> > passes such as constant propagation and dead code elimination.
>
> I don't think the QoI argument is realistic with current or forseeable
> compiler tech, at least I don't see such optimizations taking place with
> unique_lock vs. lock_guard in real code bases beyond some simple cases.
> And I'd like to emphasize that in my experience unconditional scope
> guards are much more prevalent to the dismissable ones.
I would still argue for the compiler optimisation. Quite frankly, I'm not
seeing how difficult it is: a variable that is only written to once, with a
known constant value, need not be stored in memory anywhere. The compiler can
constant-propagate it to the places where it's used, meaning there shouldn't
be any conditionals in the code.
Additionally, how costly is it if the compiler fails to do this? How much
real-world benefit do we get with this simpler class? Contrast that with how
much we pay for having it: it's a cost for implementers, it's a cost for
developers to learn that this exists, and it's a cost for reviewers to
understand what it does.
Take the example of scope_lock and unique_lock: without looking up the
reference documentation, I can't tell you the difference between them and why I
should use one and not the other. I can probably guess based on the name.
> Sure, the standard may simply require a certain behavior, and I did not
> intend my proposed solution to be adopted or even be part of the
> standard wording. I simply indicated that there is a way to achieve
> this, which is something that is welcome for any proposal, isn't it?
Oh, sure.
> > For most cases, this can be implemented already, with the ability to
> > deactivate a scope, because the typical use of such guards is to have them
> > execute on early returns. At the moment when we've achieved success, it
> > can be then dismissed -- compiler optimisations detecting such conditions
> > would be welcome too.
>
> This would be error-prone if there are multiple successful (or failure)
> return points. Scope guards are specifically aimed to relieve you from
> having to worry about missing the action upon leaving the scope, and
> having to manually (de)activate the guard before returning goes against
> this goal.
I disagree that it is error-prone. So long as all the early returns (including
throws) are of the same category -- either success or failure -- then you
simply keep the guard active, until the point when you've assured success. At
that point, you simply dismiss it.
I agree that it's not a full solution. I'm simply saying that I expect this to
be a sufficient solution for 90% of the cases. Though, admittedly, I have no
clue about co-routines and suspended states, so I may be missing something
important there.
> > For those scenarios, why can't the user-provided lambda that is executed
> > decide whether to do anything at all? Why do we need to give the scope
> > guard object two lambdas: one to decide whether to run and the other to
> > run?
> Yes, scope_success/scope_fail can be implemented in terms of scope_exit,
> but their benefit is reducing code duplication. This is most apparent
> when the failure condition is the thrown exception - you simply don't
> have to write the code for testing whether an exception is in flight.
Agreed on detecting exceptions as a failure. See below.
> This is also the case for other common cases, like testing whether an
> error code indicates error. The condition function need not be a lambda,
> it can be a function object that is written once and then reused in
> multiple scope guards.
Such an object can simply be captured and queried inside the lambda of the
guard action.
I understand the convenience value, but I am questioning whether it's worth
the cost for implementers as well as the cognitive load for users: will they
remember to use this variant instead of the generic one?
I think if you want to argue on the need for this, then it needs to carry a
reasonable example that couldn't be simply rewritten with an extra "if" inside
the lambda.
> There is also value in code readability. Seeing scope_fail will
> immediately give you the idea that this is an error handler rather than
> a general cleanup.
I agree scope_fail is useful for the "is exception active" detection.
I am guessing you're arguing that, as scope_fail may already be in use in some
contexts due to its ability to detect when an exception is active, it might be
useful to allow the user to extend it to add more failure conditions.
What I was thinking was on the case that the user was trying to use scope_fail
without exceptions active: I don't see the point. They may as well use a plain
scope_guard and have their error condition checked inside the lambda. Or, as
argued above, not do any checking and dismiss when success has been assured.
> > Can this simply be QoI? The control variable of a dismissable scope guard
> > that isn't dismissed can itself be garbage-collected after optimisation
> > passes such as constant propagation and dead code elimination.
>
> I don't think the QoI argument is realistic with current or forseeable
> compiler tech, at least I don't see such optimizations taking place with
> unique_lock vs. lock_guard in real code bases beyond some simple cases.
> And I'd like to emphasize that in my experience unconditional scope
> guards are much more prevalent to the dismissable ones.
I would still argue for the compiler optimisation. Quite frankly, I'm not
seeing how difficult it is: a variable that is only written to once, with a
known constant value, need not be stored in memory anywhere. The compiler can
constant-propagate it to the places where it's used, meaning there shouldn't
be any conditionals in the code.
Additionally, how costly is it if the compiler fails to do this? How much
real-world benefit do we get with this simpler class? Contrast that with how
much we pay for having it: it's a cost for implementers, it's a cost for
developers to learn that this exists, and it's a cost for reviewers to
understand what it does.
Take the example of scope_lock and unique_lock: without looking up the
reference documentation, I can't tell you the difference between them and why I
should use one and not the other. I can probably guess based on the name.
-- Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org Software Architect - Intel DCAI Cloud Engineering
Received on 2023-04-08 00:55:02