[scope] some more thoughts

Hi Everyone, I want to share some thoughts regarding the review of Boost.Scope. The fact that it wants to implement 1-to-1 a feature from a TS -- not from the standard -- impedes the review process. The reviewer says, "this feature is bad". The author responds, "but this is how the TS requires it to be". And now what? Should we accept a bad feature because the TS requires so? Being TS-conformant is very constraining for the author. Maybe this is not a worthy goal. Also, my observation from recent years is that putting stuff into a TS may be a polite way of saying, "we respect you, but we do not want this." Also, the goal of having an implementation for a proposed feature is to check if the interface has been battle-tested to be sound. The Boost Review process is such a test. When the feedback is, this feature is wrong, then this feedback should affect the STD proposal. But Boost shouldn't be compromised by an STD proposal that was submitted without a battle-tested implementation. The move assignment operator in scope_exit and friends. It looks like its existence is against the goal of the feature. You define a guard and then you use it. Why would you assign a different guard to it? Why do we have scope_success? An alternative to using scope success is to just put your instructions at the end of the function. Do you have multiple return statements in your function and at the same time you need the same sequence of operations to be performed at each end? This is a good motivation to restructure your function to have a single return. Multiple returns are generally OK, but needing the same sequence warrants a single return. scope_success seems like a solution that is looking for a problem. "But the TS has scope_success" -- this is not a good argument for me. In Boost Review we should be evaluating a library based on its own merit, not based on a similarity with some other design, which can have its own flaws. Mixing unique_resource with scope guards is not a good idea. The STD tends to use big aggregate headers. Boost does not. The solution from the TS should not be ported 1-to-1 to Boost. Boost.Scope offers at least three ways to do the same thing. For instance, to execute some code only upon some inconvenient condition, I can use scope_fail with my condition, or scope_exit with my condition, or I can add set_active on top of either. I consider this to be a bad design. Source code is not only for the flexibility of writing, but also for the ease of reading and learning. Having multiple ways to do the same thing impedes the understanding of the code. While I understand the existence of the triplet `scope_exit`, `scope_fail` and `scope_success` (D uses an analogous triplet), it takes a while to understand the difference between scope_exit and scope_final. And the choice of name doesn't help there. The Standard library chose to use term guard (std::lock_guard) to indicate something small and cheap. Maybe use this name instead. Not perfect either, but at least consistent with what we have in STD. Also, my experience is that one uses scope guards only in rare situations where you need to satisfy complex guarantees, like maintaining the same size of two vectors. And in those situations one is already very cautious, and tries to structure the code for the minimum surprises. One does not use multiple returns or complex control flows. The justification I heard for so many features in Boost.Scope is that there may be complex situations that require it. I think that in these situations restructuring the code is better than adding more features to Boost.Scope. This looks like a lot of harsh words, but I find a subset of the library very useful: * the unconditional execution of the function * the execution based on manual deactivation (active being the initial state) * execution based on the uncaught exception count, with the caveat that it sometimes doesn't work, and it is not cheap. Regards, &rzej;

On 12/3/23 16:50, Andrzej Krzemienski via Boost wrote:
Hi Everyone, I want to share some thoughts regarding the review of Boost.Scope. The fact that it wants to implement 1-to-1 a feature from a TS -- not from the standard -- impedes the review process. The reviewer says, "this feature is bad". The author responds, "but this is how the TS requires it to be". And now what? Should we accept a bad feature because the TS requires so?
If you think a certain feature is badly designed or in any way harmful, you are free to reflect that in your review. That's what the review process is for. When I say that this or that is done so because of the TS, I'm explaining my reasoning behind doing it this way. This is not to say "we must accept the library because TS". If the community doesn't think compatibility with the TS is a worthy goal, then let that be the result of the review.
The move assignment operator in scope_exit and friends. It looks like its existence is against the goal of the feature. You define a guard and then you use it. Why would you assign a different guard to it?
Scope guards are *not* move assignable.
Why do we have scope_success? An alternative to using scope success is to just put your instructions at the end of the function. Do you have multiple return statements in your function and at the same time you need the same sequence of operations to be performed at each end? This is a good motivation to restructure your function to have a single return. Multiple returns are generally OK, but needing the same sequence warrants a single return.
What if having a single return is difficult to achieve? Sprinkling `goto finish` doesn't work well sometimes, e.g. when you have variable definitions in the middle of the function. It is also more prone to errors.
While I understand the existence of the triplet `scope_exit`, `scope_fail` and `scope_success` (D uses an analogous triplet), it takes a while to understand the difference between scope_exit and scope_final. And the choice of name doesn't help there. The Standard library chose to use term guard (std::lock_guard) to indicate something small and cheap. Maybe use this name instead. Not perfect either, but at least consistent with what we have in STD.
I could rename scope_final to something else, especially given that Peter has requested to rename BOOST_SCOPE_FINAL for BOOST_SCOPE_DEFER, but I haven't seen a good name for it. scope_guard sounds too generic to me, and it surely doesn't make the distinction from scope_exit any more obvious. scope_defer doesn't make much sense from the English point of view (though, I'm not a native speaker).

niedz., 3 gru 2023 o 15:56 Andrey Semashev via Boost <boost@lists.boost.org> napisał(a):
On 12/3/23 16:50, Andrzej Krzemienski via Boost wrote:
Hi Everyone, I want to share some thoughts regarding the review of Boost.Scope. The fact that it wants to implement 1-to-1 a feature from a TS -- not from the standard -- impedes the review process. The reviewer says, "this feature is bad". The author responds, "but this is how the TS requires it to be". And now what? Should we accept a bad feature because the TS requires so?
If you think a certain feature is badly designed or in any way harmful, you are free to reflect that in your review. That's what the review process is for.
When I say that this or that is done so because of the TS, I'm explaining my reasoning behind doing it this way. This is not to say "we must accept the library because TS". If the community doesn't think compatibility with the TS is a worthy goal, then let that be the result of the review.
The move assignment operator in scope_exit and friends. It looks like its existence is against the goal of the feature. You define a guard and then you use it. Why would you assign a different guard to it?
Scope guards are *not* move assignable.
Correct. My bad. Sorry. I misread the assignment declarations.
Why do we have scope_success? An alternative to using scope success is to just put your instructions at the end of the function. Do you have multiple return statements in your function and at the same time you need the same sequence of operations to be performed at each end? This is a good motivation to restructure your function to have a single return. Multiple returns are generally OK, but needing the same sequence warrants a single return.
What if having a single return is difficult to achieve? Sprinkling `goto finish` doesn't work well sometimes, e.g. when you have variable definitions in the middle of the function. It is also more prone to errors.
Further discussion would be easier if you were able to provide an example of such a function. It may be just my lacking imagination, but in my experience any complicated function could be refactored, e.g. some parts split to separate functions or lambdas, so that a single return could be achieved, or multiple returns without identical sequence of operations. A good example could prove me wrong.
While I understand the existence of the triplet `scope_exit`, `scope_fail` and `scope_success` (D uses an analogous triplet), it takes a while to understand the difference between scope_exit and scope_final. And the choice of name doesn't help there. The Standard library chose to use term guard (std::lock_guard) to indicate something small and cheap. Maybe use this name instead. Not perfect either, but at least consistent with what we have in STD.
I could rename scope_final to something else, especially given that Peter has requested to rename BOOST_SCOPE_FINAL for BOOST_SCOPE_DEFER, but I haven't seen a good name for it. scope_guard sounds too generic to me, and it surely doesn't make the distinction from scope_exit any more obvious. scope_defer doesn't make much sense from the English point of view (though, I'm not a native speaker).
scope_exit_lite. Longer, but I think it conveys the idea. Regards, &rzej;

On 12/3/23 19:26, Andrzej Krzemienski wrote:
niedz., 3 gru 2023 o 15:56 Andrey Semashev via Boost <boost@lists.boost.org <mailto:boost@lists.boost.org>> napisał(a):
> Why do we have scope_success? An alternative to using scope success is to > just put your instructions at the end of the function. Do you have multiple > return statements in your function and at the same time you need the same > sequence of operations to be performed at each end? This is a good > motivation to restructure your function to have a single return. Multiple > returns are generally OK, but needing the same sequence warrants a single > return.
What if having a single return is difficult to achieve? Sprinkling `goto finish` doesn't work well sometimes, e.g. when you have variable definitions in the middle of the function. It is also more prone to errors.
Further discussion would be easier if you were able to provide an example of such a function. It may be just my lacking imagination, but in my experience any complicated function could be refactored, e.g. some parts split to separate functions or lambdas, so that a single return could be achieved, or multiple returns without identical sequence of operations. A good example could prove me wrong.
Ok, here's a candidate that could use a scope_success. The method handles an incoming RTSP request. It is supposed to analyze the request validity, parse its common headers, find a session which the request belongs to and invoke a handler in that session. In any case, the method is supposed to send a response to that request - either a positive 200 or an error. Naturally, depending on the outcome, there may be different additional headers that need to be added to the response. Extremely simplified, it currently looks something like this: void receiver::on_request(request const& req) { response resp; { auto it = req.headers().find(cseq); if (req.start_line().version == protocol_version::unknown) { resp.start_line().version = m_protocol_version; resp.start_line().set_status(version_not_supported); if (it != req.headers().end()) resp.headers().set(cseq, it->value(), it->is_sensitive()); resp.set_body(text_plain, rtsp_version_not_supported_body); goto send_response; } resp.start_line().version = req.start_line().version; if (it == req.headers().end()) { resp.start_line().set_status(bad_request); goto send_response; } // ... and so on, continue to validate the request, filling // the response as needed, based on the request, and jumping // to send_response below in case of errors } { rtsp_session* rtsp_sess = find_session(req); if (!rtsp_sess) { resp.start_line().set_status(session_not_found); goto send_response; } switch (req.start_line().method) { // For each supported method, analyze request headers // that pertain to that method, invoke a handler in the // session or otherwise handle it. Then either break or // goto send_response. } // At this point the request is handled successfully resp.start_line().set_status(ok); } send_response: send_message(resp, [this](error_code const& error) { // Handle sending errors, if any }); } You can see the pattern here. resp gets populated according to the request and the processing results, then it gets sent in the end. This code could be simplified by wrapping the send_message call in a scope_success at the beginning of the function. This would remove the need to introduce additional scopes to make the goto work, and would allow to replace all gotos with returns. I could also accompany it with a scope_fail to send a special response in case of exception. (Currently, exceptions are handled elsewhere.) void receiver::on_request(request const& req) { response resp; scope_success success_guard{[&] { send_message(resp, [this](error_code const& error) { // Handle sending errors, if any }); }}; scope_fail failure_guard{[&] { send_fatal_response_and_disconnect(resp); // doesn't throw }}; auto it = req.headers().find(cseq); if (req.start_line().version == protocol_version::unknown) { // ... return; } // etc. } Also, separate from the example above, a few other use cases. Although not quite the same as scope_success, it appears I'm using a similar concept in Boost.Log in a couple of places: https://github.com/boostorg/log/blob/8b921b6352e76ff54f5674c864d1f4f749c9158... https://github.com/boostorg/log/blob/8b921b6352e76ff54f5674c864d1f4f749c9158... In both cases, the classes work a "pumps". I.e. they initiate data collection on construction, then the user provides the data (by calling operator<< or operator% to format the log message), then on destruction, if no exception was thrown, the pump pushes the collected data to the logger. So, for example, in the following expression: BOOST_LOG(lg) << "Hello " << "world!"; BOOST_LOG macro creates the record_pump object, then the operator<< calls compose the log message, then the record_pump object is destroyed and, if the operator<< calls didn't throw, pushes the log record into the logger. If an exception is thrown then the message is discarded. This design is influenced by the required syntax - I need to push the record *after* the message is formatted. But I believe, such a pattern is not uncommon and could use scope_success for this purpose. In a way, the previous example with RTSP request handling also fits this pattern - you create a scope guard, then you populate some data structure, then, if everything is ok, you send that data structure for further processing. I will also note that there are a few examples in D documentation regarding scope guards: https://dlang.org/articles/exception-safe.html

pon., 4 gru 2023 o 00:29 Andrey Semashev via Boost <boost@lists.boost.org> napisał(a):
On 12/3/23 19:26, Andrzej Krzemienski wrote:
niedz., 3 gru 2023 o 15:56 Andrey Semashev via Boost <boost@lists.boost.org <mailto:boost@lists.boost.org>> napisał(a):
> Why do we have scope_success? An alternative to using scope success is to > just put your instructions at the end of the function. Do you have multiple > return statements in your function and at the same time you need the same > sequence of operations to be performed at each end? This is a good > motivation to restructure your function to have a single return. Multiple > returns are generally OK, but needing the same sequence warrants a single > return.
What if having a single return is difficult to achieve? Sprinkling `goto finish` doesn't work well sometimes, e.g. when you have variable definitions in the middle of the function. It is also more prone to errors.
Further discussion would be easier if you were able to provide an example of such a function. It may be just my lacking imagination, but in my experience any complicated function could be refactored, e.g. some parts split to separate functions or lambdas, so that a single return could be achieved, or multiple returns without identical sequence of operations. A good example could prove me wrong.
Ok, here's a candidate that could use a scope_success. The method handles an incoming RTSP request. It is supposed to analyze the request validity, parse its common headers, find a session which the request belongs to and invoke a handler in that session. In any case, the method is supposed to send a response to that request - either a positive 200 or an error. Naturally, depending on the outcome, there may be different additional headers that need to be added to the response.
Extremely simplified, it currently looks something like this:
void receiver::on_request(request const& req) { response resp;
{ auto it = req.headers().find(cseq); if (req.start_line().version == protocol_version::unknown) { resp.start_line().version = m_protocol_version; resp.start_line().set_status(version_not_supported);
if (it != req.headers().end()) resp.headers().set(cseq, it->value(), it->is_sensitive());
resp.set_body(text_plain, rtsp_version_not_supported_body);
goto send_response; }
resp.start_line().version = req.start_line().version;
if (it == req.headers().end()) { resp.start_line().set_status(bad_request); goto send_response; }
// ... and so on, continue to validate the request, filling // the response as needed, based on the request, and jumping // to send_response below in case of errors }
{ rtsp_session* rtsp_sess = find_session(req); if (!rtsp_sess) { resp.start_line().set_status(session_not_found); goto send_response; }
switch (req.start_line().method) { // For each supported method, analyze request headers // that pertain to that method, invoke a handler in the // session or otherwise handle it. Then either break or // goto send_response. }
// At this point the request is handled successfully resp.start_line().set_status(ok); }
send_response: send_message(resp, [this](error_code const& error) { // Handle sending errors, if any }); }
You can see the pattern here. resp gets populated according to the request and the processing results, then it gets sent in the end.
This code could be simplified by wrapping the send_message call in a scope_success at the beginning of the function. This would remove the need to introduce additional scopes to make the goto work, and would allow to replace all gotos with returns. I could also accompany it with a scope_fail to send a special response in case of exception. (Currently, exceptions are handled elsewhere.)
void receiver::on_request(request const& req) { response resp;
scope_success success_guard{[&] { send_message(resp, [this](error_code const& error) { // Handle sending errors, if any }); }};
scope_fail failure_guard{[&] { send_fatal_response_and_disconnect(resp); // doesn't throw }};
auto it = req.headers().find(cseq); if (req.start_line().version == protocol_version::unknown) { // ... return; }
// etc. }
Also, separate from the example above, a few other use cases. Although not quite the same as scope_success, it appears I'm using a similar concept in Boost.Log in a couple of places:
https://github.com/boostorg/log/blob/8b921b6352e76ff54f5674c864d1f4f749c9158...
https://github.com/boostorg/log/blob/8b921b6352e76ff54f5674c864d1f4f749c9158...
In both cases, the classes work a "pumps". I.e. they initiate data collection on construction, then the user provides the data (by calling operator<< or operator% to format the log message), then on destruction, if no exception was thrown, the pump pushes the collected data to the logger. So, for example, in the following expression:
BOOST_LOG(lg) << "Hello " << "world!";
BOOST_LOG macro creates the record_pump object, then the operator<< calls compose the log message, then the record_pump object is destroyed and, if the operator<< calls didn't throw, pushes the log record into the logger. If an exception is thrown then the message is discarded.
This design is influenced by the required syntax - I need to push the record *after* the message is formatted. But I believe, such a pattern is not uncommon and could use scope_success for this purpose. In a way, the previous example with RTSP request handling also fits this pattern - you create a scope guard, then you populate some data structure, then, if everything is ok, you send that data structure for further processing.
I will also note that there are a few examples in D documentation regarding scope guards:
Thanks. The "before" and "after" samples seem to do a different thing (the "after" also displays the failure message). But I thing the "before example could be refactored to: void receiver::on_request(request const& req) { response resp = [&] // IILF { auto it = req.headers().find(cseq); if (req.start_line().version == protocol_version::unknown) { resp.start_line().version = m_protocol_version; resp.start_line().set_status(version_not_supported); if (it != req.headers().end()) resp.headers().set(cseq, it->value(), it->is_sensitive()); resp.set_body(text_plain, rtsp_version_not_supported_body); return resp; } resp.start_line().version = req.start_line().version; if (it == req.headers().end()) { resp.start_line().set_status(bad_request); return resp; } // ... and so on, continue to validate the request, filling // the response as needed, based on the request, and jumping // to send_response below in case of errors rtsp_session* rtsp_sess = find_session(req); if (!rtsp_sess) { resp.start_line().set_status(session_not_found); return resp; } switch (req.start_line().method) { // } // At this point the request is handled successfully resp.start_line().set_status(ok); return resp; }(); send_message(resp, [this](error_code const& error) { // Handle sending errors, if any }); } The D documentation examples seem artificial.I think yours is better. Aldo, one observation regarding this piece of code: scope_success success_guard{[&] { send_message(resp, [this](error_code const& error) { // Handle sending errors, if any }); }}; scope_fail failure_guard{[&] { send_fatal_response_and_disconnect(resp); // doesn't throw }}; This use case is not served best by two scope guards, as each of them incurs the same cost of exception checking. Maybe it would be served better by a yet another guard type that takes two callbacks: one representing the action on success, the other the action on failure. One other, unrelated, observation. When I need to make sure that three things happen together or none does: the most efficient way to do it seems to be: void update_three(Compo const& val) { bool armed = true; vec1.push_back(val); BOOST_SCOPE_FINAL [&] { if (armed) vec1.pop_back(); }; vec2.push_back(val); BOOST_SCOPE_FINAL [&] { if (armed) vec2.pop_back(); }; vec3.push_back(val); armed = false; } Regards, &rzej;

On 12/5/23 02:13, Andrzej Krzemienski wrote:
pon., 4 gru 2023 o 00:29 Andrey Semashev via Boost <boost@lists.boost.org <mailto:boost@lists.boost.org>> napisał(a):
On 12/3/23 19:26, Andrzej Krzemienski wrote: > niedz., 3 gru 2023 o 15:56 Andrey Semashev via Boost > <boost@lists.boost.org <mailto:boost@lists.boost.org> <mailto:boost@lists.boost.org <mailto:boost@lists.boost.org>>> napisał(a): > > > Why do we have scope_success? An alternative to using scope > success is to > > just put your instructions at the end of the function. Do you have > multiple > > return statements in your function and at the same time you need > the same > > sequence of operations to be performed at each end? This is a good > > motivation to restructure your function to have a single return. > Multiple > > returns are generally OK, but needing the same sequence warrants a > single > > return. > > What if having a single return is difficult to achieve? Sprinkling `goto > finish` doesn't work well sometimes, e.g. when you have variable > definitions in the middle of the function. It is also more prone to > errors. > > Further discussion would be easier if you were able to provide an > example of such a function. > It may be just my lacking imagination, but in my experience any > complicated function could be > refactored, e.g. some parts split to separate functions or lambdas, so > that a single return could be achieved, or multiple returns without > identical sequence of operations. > A good example could prove me wrong.
Ok, here's a candidate that could use a scope_success. The method handles an incoming RTSP request. It is supposed to analyze the request validity, parse its common headers, find a session which the request belongs to and invoke a handler in that session. In any case, the method is supposed to send a response to that request - either a positive 200 or an error. Naturally, depending on the outcome, there may be different additional headers that need to be added to the response.
Extremely simplified, it currently looks something like this:
void receiver::on_request(request const& req) { response resp;
{ auto it = req.headers().find(cseq); if (req.start_line().version == protocol_version::unknown) { resp.start_line().version = m_protocol_version; resp.start_line().set_status(version_not_supported);
if (it != req.headers().end()) resp.headers().set(cseq, it->value(), it->is_sensitive());
resp.set_body(text_plain, rtsp_version_not_supported_body);
goto send_response; }
resp.start_line().version = req.start_line().version;
if (it == req.headers().end()) { resp.start_line().set_status(bad_request); goto send_response; }
// ... and so on, continue to validate the request, filling // the response as needed, based on the request, and jumping // to send_response below in case of errors }
{ rtsp_session* rtsp_sess = find_session(req); if (!rtsp_sess) { resp.start_line().set_status(session_not_found); goto send_response; }
switch (req.start_line().method) { // For each supported method, analyze request headers // that pertain to that method, invoke a handler in the // session or otherwise handle it. Then either break or // goto send_response. }
// At this point the request is handled successfully resp.start_line().set_status(ok); }
send_response: send_message(resp, [this](error_code const& error) { // Handle sending errors, if any }); }
You can see the pattern here. resp gets populated according to the request and the processing results, then it gets sent in the end.
This code could be simplified by wrapping the send_message call in a scope_success at the beginning of the function. This would remove the need to introduce additional scopes to make the goto work, and would allow to replace all gotos with returns. I could also accompany it with a scope_fail to send a special response in case of exception. (Currently, exceptions are handled elsewhere.)
void receiver::on_request(request const& req) { response resp;
scope_success success_guard{[&] { send_message(resp, [this](error_code const& error) { // Handle sending errors, if any }); }};
scope_fail failure_guard{[&] { send_fatal_response_and_disconnect(resp); // doesn't throw }};
auto it = req.headers().find(cseq); if (req.start_line().version == protocol_version::unknown) { // ... return; }
// etc. }
Also, separate from the example above, a few other use cases. Although not quite the same as scope_success, it appears I'm using a similar concept in Boost.Log in a couple of places:
https://github.com/boostorg/log/blob/8b921b6352e76ff54f5674c864d1f4f749c9158... <https://github.com/boostorg/log/blob/8b921b6352e76ff54f5674c864d1f4f749c9158b/include/boost/log/sources/record_ostream.hpp#L460-L539>
https://github.com/boostorg/log/blob/8b921b6352e76ff54f5674c864d1f4f749c9158... <https://github.com/boostorg/log/blob/8b921b6352e76ff54f5674c864d1f4f749c9158b/include/boost/log/detail/format.hpp#L246-L331>
In both cases, the classes work a "pumps". I.e. they initiate data collection on construction, then the user provides the data (by calling operator<< or operator% to format the log message), then on destruction, if no exception was thrown, the pump pushes the collected data to the logger. So, for example, in the following expression:
BOOST_LOG(lg) << "Hello " << "world!";
BOOST_LOG macro creates the record_pump object, then the operator<< calls compose the log message, then the record_pump object is destroyed and, if the operator<< calls didn't throw, pushes the log record into the logger. If an exception is thrown then the message is discarded.
This design is influenced by the required syntax - I need to push the record *after* the message is formatted. But I believe, such a pattern is not uncommon and could use scope_success for this purpose. In a way, the previous example with RTSP request handling also fits this pattern - you create a scope guard, then you populate some data structure, then, if everything is ok, you send that data structure for further processing.
I will also note that there are a few examples in D documentation regarding scope guards:
https://dlang.org/articles/exception-safe.html <https://dlang.org/articles/exception-safe.html>
Thanks.
The "before" and "after" samples seem to do a different thing (the "after" also displays the failure message).
Um, no, where did you see that? The only difference is the added failure_guard.
But I thing the "before example could be refactored to:
void receiver::on_request(request const& req) { response resp = [&] // IILF {
Presumably, you also need to construct resp here.
auto it = req.headers().find(cseq); if (req.start_line().version == protocol_version::unknown) { resp.start_line().version = m_protocol_version; resp.start_line().set_status(version_not_supported);
if (it != req.headers().end()) resp.headers().set(cseq, it->value(), it->is_sensitive());
resp.set_body(text_plain, rtsp_version_not_supported_body);
return resp; }
resp.start_line().version = req.start_line().version;
if (it == req.headers().end()) { resp.start_line().set_status(bad_request); return resp; }
// ... and so on, continue to validate the request, filling // the response as needed, based on the request, and jumping // to send_response below in case of errors rtsp_session* rtsp_sess = find_session(req); if (!rtsp_sess) { resp.start_line().set_status(session_not_found); return resp; }
switch (req.start_line().method) { // }
// At this point the request is handled successfully resp.start_line().set_status(ok); return resp; }();
send_message(resp, [this](error_code const& error) { // Handle sending errors, if any }); }
That would add an extra copy or move of response to return from the lambda, but yes, that could work. I guess, it's a matter of how easy it is to perform this transformation and how much overhead it would be. And also a personal preference.
Aldo, one observation regarding this piece of code:
scope_success success_guard{[&] { send_message(resp, [this](error_code const& error) { // Handle sending errors, if any }); }};
scope_fail failure_guard{[&] { send_fatal_response_and_disconnect(resp); // doesn't throw }};
This use case is not served best by two scope guards, as each of them incurs the same cost of exception checking. Maybe it would be served better by a yet another guard type that takes two callbacks: one representing the action on success, the other the action on failure.
Yes, the exception would be checked twice, but I don't expect this to be expensive. The idea of a scope guard with two callbacks also occurred to me, but it would probably be better to do something like this instead: BOOST_SCOPE_FINAL [&, check_ex = exception_checker()] { if (!check_ex()) { // No exception } else { // Exception was thrown } };
One other, unrelated, observation. When I need to make sure that three things happen together or none does: the most efficient way to do it seems to be:
void update_three(Compo const& val) { bool armed = true;
vec1.push_back(val); BOOST_SCOPE_FINAL [&] { if (armed) vec1.pop_back(); };
vec2.push_back(val); BOOST_SCOPE_FINAL [&] { if (armed) vec2.pop_back(); };
vec3.push_back(val); armed = false; }
I would use scope_fail instead of both BOOST_SCOPE_FINAL and `armed`.
participants (2)
-
Andrey Semashev
-
Andrzej Krzemienski