SCOPE#

SCOPE(...)#

Automatically runs code when a scope is exited (SCOPE(exit)), exited by means of an exception (SCOPE(fail)), or exited normally (SCOPE(success)).

A guard body that throws is treated the way the language treats exceptions: one at a time is an error to report, two at once is fatal. SCOPE(exit) propagates a failure raised while the scope is being left NORMALLY, so a cleanup that fails on the happy path reaches the caller as an ordinary exception; a failure raised while the scope is ALREADY unwinding is reported (message and location) and aborts, since letting it escape would give std::terminate with no diagnostic. SCOPE(fail) runs only while unwinding and therefore always reports and aborts; SCOPE(success) runs only on the normal path and may throw. In all cases the controlled code must return void (enforced at compile time).

One consequence worth knowing: inside a noexcept function, a SCOPE(exit) body that throws on the normal path ends the program through std::terminate without STF’s report. Use SCOPE(fail) plus SCOPE(success) there if the distinction matters.

SCOPE(exit) runs its code at the natural termination of the current scope. Example:

  // SCOPE(exit) runs the lambda upon the termination of the current scope.
  bool done = false;
  {
    SCOPE(exit)
    {
      done = true;
    };
    EXPECT(!done, "SCOPE_EXIT should not run early.");
  }
  EXPECT(done);

SCOPE(exit, name) additionally names a bool parameter that is true when the scope is being left by an exception thrown within it, letting one guard serve both outcomes:

SCOPE(exit, failing)
{
  failing ? discard(handle) : commit(handle);
};
The flag compares std::uncaught_exceptions() against its value at construction, so a guard declared inside somebody else’s handler correctly reports false. SCOPE(fail) and SCOPE(success) reject the parameter: their outcome is already known by construction. The exception itself is not available to a guard (a destructor is not a handler, and std::current_exception() is null during unwinding); to react to the exception, wrap the region in ON_THROW(policy).

SCOPE(fail) runs its code if and only if the current scope is left by means of throwing an exception. Example:

  bool done = false;
  {
    SCOPE(fail)
    {
      done = true;
    };
    EXPECT(!done, "SCOPE_FAIL should not run early.");
  }
  EXPECT(!done);

  try
  {
    SCOPE(fail)
    {
      done = true;
    };
    EXPECT(!done);
    throw 42;
  }
  catch (...)
  {
    EXPECT(done);
  }

Finally, SCOPE(success) runs its code if and only if the current scope is left by normal flow (as opposed to by an exception). Example:

  bool done = false;
  {
    SCOPE(success)
    {
      done = true;
    };
    EXPECT(!done);
  }
  EXPECT(done);
  done = false;

  try
  {
    SCOPE(success)
    {
      done = true;
    };
    EXPECT(!done);
    throw 42;
  }
  catch (...)
  {
    EXPECT(!done);
  }

If two or more SCOPE declarations are present in the same scope, they will take effect in the reverse order of their lexical order. Example:

  int counter = 0;
  {
    SCOPE(exit)
    {
      EXPECT(counter == 2);
      counter = 0;
    };
    SCOPE(success)
    {
      EXPECT(counter == 1);
      ++counter;
    };
    SCOPE(exit)
    {
      EXPECT(counter == 0);
      ++counter;
    };
    EXPECT(counter == 0);
  }
  EXPECT(counter == 0);

See Also: https://en.cppreference.com/w/cpp/experimental/scope_exit, https://en.cppreference.com/w/cpp/experimental/scope_fail, https://en.cppreference.com/w/cpp/experimental/scope_success