Custom exception ptr

If you merely want result to capture stack backtraces without calling a memory allocator and retaining any triviality of copy which is important for optimisation, you already have everything you need.

But let’s keep going by intercepting any construction of our localised outcome from our localised result, retrieving any stored backtrace and using it to synthesise an exception ptr with a message text including the backtrace. Firstly let us look at the function which synthesises the exception ptr:

namespace error_code_extended
{
  // Synthesise a custom exception_ptr from the TLS slot and write it into the outcome
  template <class R> inline void poke_exception(outcome<R> *o)
  {
    if(o->has_error())
    {
      extended_error_info *eei = mythreadlocaldata().get(OUTCOME_V2_NAMESPACE::hooks::spare_storage(o));
      if(eei != nullptr)
      {
        // Make a custom string for the exception
        std::string str(o->error().message());
        str.append(" [");
        struct unsymbols  // RAII cleaner for symbols
        {
          char **_{nullptr};
          ~unsymbols() { ::free(_); }
        } symbols{::backtrace_symbols(eei->backtrace.data(), eei->items)};
        if(symbols._ != nullptr)
        {
          for(size_t n = 0; n < eei->items; n++)
          {
            if(n > 0)
            {
              str.append("; ");
            }
            str.append(symbols._[n]);
          }
        }
        str.append("]");

        // Override the payload/exception member in the outcome with our synthesised exception ptr
        OUTCOME_V2_NAMESPACE::hooks::override_outcome_exception(o, std::make_exception_ptr(std::runtime_error(str)));
      }
    }
  }
}
View this code on Github

If the localised outcome being constructed is errored, try fetching the TLS slot for the unique 16-bit value in its spare storage. If that is valid, symbolise the stack backtrace into a string and make an exception ptr with a runtime error with that string. Finally, override the payload/exception member in our just-copy-constructed localised outcome with the new exception ptr.


As the reference documentation for void override_outcome_exception(basic_outcome<T, EC, EP, NoValuePolicy> *, U &&) noexcept points out, you almost certainly never want to use this function if there is any other alternative. It is worth explaining what is meant by this.

In this section, we always synthesise an exception ptr from the stored state and error code at the exact point of transition from result based APIs to outcome based APIs. This is acceptable only because we know that our code enforces that discipline.

If one were designing a library facility, one could not assume such discipline in the library user. One would probably be better off making the exception ptr synthesis lazy via a custom no-value policy which generates the stacktrace-containing error message only on demand e.g. .exception() observation, or a .value() observation where no value is available.

Such a design is however more indeterminate than the design presented in this section, because the indeterminacy is less predictable than in this design. Ultimately which strategy you adopt depends on how important absolute determinism is to your Outcome-based application.