Boost logo

Boost :

Subject: Re: [boost] [lclcontext] Any interest in Pythonesque "with-as" statement in C++.
From: Sam Kellett (samkellett_at_[hidden])
Date: 2015-08-17 21:13:41


> This should work with C++11:
>>
>> template <typename T, typename F>
>> void with(T &&t, F &&fn)
>> {
>> fn(std::forward<T>(t));
>> }
>>
>> with(make_foo(), [](foo &&f) {
>> // do things with foo.
>> }); // foo is destroyed.
>>
>> Don't think I can help w/r/t C++03 though, the lack of lambdas is a
>> killer.
>>
>>
> One thing this method doesn't do is automatically determine if the scope
> exit prematurely.

hmm... scanning your documentation do you mean the scope_completed flag?

ok so if i understand correctly you're saying that foo could be a
transaction, and when foo is destructed you either want to commit or
rollback depending on whether or not the lambda succeeded? interesting...

an easy thing to do is an overload of with for objects of T that has an
on_exit method to expect a boolean lambda and call the on_exit callback
with the result of the lambda.

namespace detail {

// for types that can handle failure
template <typename T, typename F>
void with_impl(T &&t, F &&fn, std::true_type)
{
  auto value(std::forward<T>(t));

  const auto scope_completed(fn(value));
  value.on_exit(scope_completed);
}

// for other types
template <typename T, typename F>
void with_impl(T &&t, F &&fn, std::false_type)
{
  fn(std::forward<T>(t));
}

} // namespace detail

template <typename T, typename F>
void with(T &&t, F &&fn)
{
  const auto has_on_exit(boost::hana::is_valid([](auto&& x) -> decltype((
void) x.on_exit(true)) { });

  detail::with_impl(std::forward<T>(t), std::forward<F>(fn), has_on_exit);
}

quick disclaimer: written this all straight into gmail without running it.
it'll almost certainly have an error or two in but the general idea is
sound as far as i can tell. also i've used Boost.Hana for determining the
existence of the field, this is obviously c++14 only, but i put it in here
because it's so beautifully concise and doesn't get in the way of what the
example is actually trying to show. this is still possible in c++11 too i
just couldn't be bothered (nor know off by heart) all the boilerplate that
comes with such a check.

then this should work:

struct transaction {
  void on_exit(bool scope_completed) {
    if (scope_completed) {
      db.commit();
    } else {
      db.rollback();
    }
  }

  // rest of class...
};

with(make_transaction(), [](transaction &&t) {
  // returning on success of operation.
  return t.insert_1000_rows_concurrently(..);
}); // t will commit/rollback depending on success of nasty insert method.

an potentially better (and simpler!) alternative could be to (optionally)
accept another lambda argument that is the on_exit callback and to call
that instead of the class' member function.

// prototype:
with(T &&t, F &&fn, G &&on_exit);

ditto for on_enter too if you want preconditions


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk