4
while(model.condition) {
    auto data = yield_data();
    auto _= manipulate(model, data);
    model.get_info(args);
}

I have an RAII object of type manipulate, whose destructor undoes the mutation it causes when it falls out of scope, much like std::lock_guard. The problem is that the user has to type auto _= or the destructor will get called before model.get_info(); I don't like that the user has to type auto _=. Why would a user think to create an object that is never used?

My first idea was to make the constructor [[nodiscard]]; but constructors have no return values. Is there a way to tell the compiler that manipulate rvalues should have lvalue lifetimes?

E_net4
  • 27,810
  • 13
  • 101
  • 139
  • You can use static code-analysis tools to guard against this kind of mistake, like clang-tidy. – super Apr 10 '19 at 11:22
  • After `register` was removed in C++17, a proposal came out to re-purpose it for prolonging the life of temporaries. I don't know what's the state of [p0577](https://wg21.link/p0577) today, but assuming the author keeps pursuing it, maybe there will be a better way in the future. Who knows. – StoryTeller - Unslander Monica Apr 10 '19 at 11:31
  • http://kera.name/articles/2012/05/when-is-a-scoped-lock-not-a-scoped-lock/ – Lightness Races in Orbit Apr 10 '19 at 12:28

4 Answers4

2

It's an unsolved problem for std::lock_guard as well, if you forget to give it a name, you get a bug.

Some tricks in here: How to avoid C++ anonymous objects

A talk about this and other pitfalls linked here: Different behavior when `std::lock_guard<std::mutex>` object has no name

1

There is no way to extend the lifetime of an rvalue beyond the full-expression it appears in without binding it to some variable. So, unfortunately, you will have to somehow turn your rvalue into an lvalue, or move the actual work into a scope that does not outlive the rvalue.

One way to achieve the latter is to use a callback as demonstrated in the other answers here.

Alternatively, thanks to guaranteed copy elision, you could turn your manipulate() into a function instead of calling the constructor directly. This would at least allow you to take advantage of the [[nodiscard]] attribute, for example:

[[nodiscard]] manipulate begin_transaction(const Model& model, const Data& data)
{
    return { model, data };
}

while(model.condition)
{
    auto data = yield_data();
    auto guard = begin_transaction(model, data);
    model.get_info(args);
}

try it out here

Michael Kenzel
  • 15,508
  • 2
  • 30
  • 39
  • 1
    @MatthieuBrucher there is no way to extend the lifetime of an rvalue beyond its full-expression without binding it to some variable. The way I understood the question is that OP was lamenting the fact that he cannot even use `[[nodiscard]]`, which the function approach presented above would allow. I edited the answer to make this more clear… – Michael Kenzel Apr 10 '19 at 11:53
0

You have to do this. No other choice.

A good example of this being indeed painful is NAG Automatic Differentiation DCO library, where you have to add lots of const auto& to your code because they rely on the destruction order to create a graph (RAII, just like you rely on this).

So would say, even use const auto& _ so that you ensure that _ doesn't get changed if this is needed.

Matthieu Brucher
  • 21,634
  • 7
  • 38
  • 62
0

Require the user to tell the manipulate ctor what code they want to run. So change it to take a callable as argument and call it:

manipulate(model, data, [&model, &args] {
    model.get_info(args);
});
Nikos C.
  • 50,738
  • 9
  • 71
  • 96