1+++ 2title = "Frequently asked questions" 3weight = 30 4+++ 5 6{{% toc %}} 7 8## Is Outcome safe to use in extern APIs? 9 10Outcome is specifically designed for use in the public interfaces of multi-million 11line codebases. `result`'s layout is hard coded to: 12 13```c 14struct 15{ 16 T value; 17 unsigned int flags; 18 EC error; 19}; 20``` 21 22This is C-compatible if `T` and `EC` are C-compatible. {{% api "std::error_code" %}} 23is *probably* C-compatible, but its layout is not standardised (though there is a 24normative note in the standard about its layout). Hence Outcome cannot provide a 25C macro API for standard Outcome, but we can for [Experimental Outcome]({{< relref "/experimental/c-api" >}}). 26 27 28## Does Outcome implement over-alignment? 29 30Variant-based alternatives to Outcome such as {{% api "std::expected<T, E>" %}} 31would use `std::aligned_union` to ensure appropriate over-alignment for the storage of 32either a `T` or an `E`. This discovers the over-alignment for a type using 33`std::alignment_of`, which is defaulted to `alignof()`. 34 35Outcome uses `struct`-based storage, as described above. Any over-alignment of 36`result` or `outcome` will follow the ordinary alignment and padding rules for 37`struct` on your compiler. Traits such as `std::alignment_of`, or other standard 38library facilities, are not used. 39 40 41## Does Outcome implement the no-fail, strong or basic exception guarantee? 42 43([You can read about the meaning of these guarantees at cppreference.com](https://en.cppreference.com/w/cpp/language/exceptions#Exception_safety)) 44 45If for the following operations: 46 47- Construction 48- Assignment 49- Swap 50 51... the corresponding operation in **all** of `value_type`, `error_type` (and 52`exception_type` for `outcome`) is `noexcept(true)`, then `result` and 53`outcome`'s operation is `noexcept(true)`. This propagates the no-fail exception 54guarantee of the underlying types. Otherwise the basic guarantee applies for all 55but Swap, under the same rules as for the `struct` layout type given above e.g. 56value would be constructed first, then the flags, then the error. If the error 57throws, value and status bits would be as if the failure had not occurred, same 58as for aborting the construction of any `struct` type. 59 60It is recognised that these weak guarantees may be unsuitable for some people, 61so Outcome implements `swap()` with much stronger guarantees, as one can locally refine, 62without too much work, one's own custom classes from `result` and `outcome` implementing 63stronger guarantees for construction and assignment using `swap()` as the primitive 64building block. 65 66The core ADL discovered implementation of strong guarantee swap is {{% api "strong_swap(bool &all_good, T &a, T &b)" %}}. 67This can be overloaded by third party code with custom strong guarantee swap 68implementations, same as for `std::swap()`. Because strong guarantee swap may fail 69when trying to restore input state during handling of failure to swap, the 70`all_good` boolean becomes false if restoration fails, at which point both 71results/outcomes get marked as tainted via {{% api "has_lost_consistency()" %}}. 72 73It is **up to you** to check this flag to see if known good state has been lost, 74as Outcome never does so on your behalf. The simple solution to avoiding having 75to deal with this situation is to always choose your value, error and exception 76types to have non-throwing move constructors and move assignments. This causes 77the strong swap implementation to no longer be used, as it is no longer required, 78and standard swap is used instead. 79 80 81## Does Outcome have a stable ABI and API? 82 83Right now, no. Though the data layout shown above is not expected to change. 84 85Outcome's ABI and API will be formally fixed as **the** v2 interface approximately 86one year after its first Boost release. Thereafter the 87[ABI compliance checker](https://lvc.github.io/abi-compliance-checker/) 88will be run per-commit to ensure Outcome's ABI and API remains stable. 89 90Note that the stable ABI and API guarantee will only apply to standalone 91Outcome, not to Boost.Outcome. Boost.Outcome has dependencies on other 92parts of Boost which are not stable across releases. 93 94Note also that the types you configure a `result` or `outcome` with also need 95to be ABI stable if `result` or `outcome` is to be ABI stable. 96 97 98## Can I use `result<T, EC>` across DLL/shared object boundaries? 99 100A known problem with using DLLs (and to smaller extent shared libraries) is that global 101objects may get duplicated: one instance in the executable and one in the DLL. This 102behaviour is not incorrect according to the C++ Standard, as the Standard does not 103recognize the existence of DLLs or shared libraries. Therefore, program designs that 104depend on globals having unique addresses may become compromised when used in a program 105using DLLs. 106 107Nothing in Outcome depends on the addresses of globals, plus the guaranteed fixed data 108layout (see answer above) means that different versions of Outcome can be used in 109different DLLs, and it probably will work okay (it is still not advised that you do that 110as that is an ODR violation). 111However, one of the most likely candidate for `EC` -- `std::error_code` -- does depend 112on the addresses of globals for correct functioning. 113 114The standard library is required to implement globally unique addresses for the standard library 115provided {{% api "std::error_category" %}} implementations e.g. `std::system_category()`. 116User defined error code categories may **not** have unique global addresses, and thus 117introduce misoperation. 118 119`boost::system::error_code`, since version 1.69 does offer an *opt-in* guarantee 120that it does not depend on the addresses of globals **if** the user defined error code 121category *opts-in* to the 64-bit comparison mechanism. This can be seen in the specification of 122`error_category::operator==` in 123[Boost.System synopsis](https://www.boost.org/doc/libs/1_69_0/libs/system/doc/html/system.html#ref_synopsis). 124 125Alternatively, the `status_code` in [Experimental Outcome](({{< relref "/experimental/differences" >}})), 126due to its more modern design, does not suffer from any problems from being used in shared 127libraries in any configuration. 128 129 130## Why two types `result<>` and `outcome<>`, rather than just one? 131 132`result` is the simple, success OR failure type. 133 134`outcome` extends `result` with a third state to transport, conventionally (but not necessarily) some sort of "abort" or "exceptional" state which a function can return to indicate that not only did the operation fail, but it did so *catastrophically* i.e. please abort any attempt to retry the operation. 135 136A perfect alternative to using `outcome` is to throw a C++ exception for the abort code path, and indeed most programs ought to do exactly that instead of using `outcome`. However there are a number of use cases where choosing `outcome` shines: 137 1381. Where C++ exceptions or RTTI is not available, but the ability to fail catastrophically without terminating the program is important. 1392. Where deterministic behaviour is required even in the catastrophic failure situation. 1403. In unit test suites of code using Outcome it is extremely convenient to accumulate test failures into an `outcome` for later reporting. A similar convenience applies to RPC situations, where C++ exception throws need to be accumulated for reporting back to the initiating endpoint. 1414. Where a function is "dual use deterministic" i.e. it can be used deterministically, in which case one switches control flow based on `.error()`, or it can be used non-deterministically by throwing an exception perhaps carrying a custom payload. 142 143 144## How badly will including Outcome in my public interface affect compile times? 145 146The quick answer is that it depends on how much convenience you want. 147 148The convenience header `<result.hpp>` is dependent on `<system_error>` or Boost.System, which unfortunately includes `<string>` and thus 149drags in quite a lot of other slow-to-parse stuff. If your public interface already includes `<string>`, 150then the impact of additionally including Outcome will be low. If you do not include `<string>`, 151unfortunately impact may be relatively quite high, depending on the total impact of your 152public interface files. 153 154If you've been extremely careful to avoid ever including the most of the STL headers 155into your interfaces in order to maximise build performance, then `<basic_result.hpp>` 156can have as few dependencies as: 157 1581. `<cstdint>` 1592. `<initializer_list>` 1603. `<iosfwd>` 1614. `<new>` 1625. `<type_traits>` 1636. `<cstdio>` 1647. `<cstdlib>` 1658. `<cassert>` 166 167These, apart from `<iosfwd>`, tend to be very low build time impact in most standard 168library implementations. If you include only `<basic_result.hpp>`, and manually configure 169`basic_result<>` by hand, compile time impact will be minimised. 170 171(See reference documentation for {{% api "basic_result<T, E, NoValuePolicy>" %}} for more detail. 172 173 174## Is Outcome suitable for fixed latency/predictable execution coding such as for high frequency trading or audio? 175 176Great care has been taken to ensure that Outcome never unexpectedly executes anything 177with unbounded execution times such as `malloc()`, `dynamic_cast<>()` or `throw`. 178Outcome works perfectly with C++ exceptions and RTTI globally disabled. 179 180Outcome's entire design premise is that its users are happy to exchange a small, predictable constant overhead 181during successful code paths, in exchange for predictable failure code paths. 182 183In contrast, table-based exception handling gives zero run time overhead for the 184successful code path, and completely unpredictable (and very expensive) overhead 185for failure code paths. 186 187For code where predictability of execution, no matter the code path, is paramount, 188writing all your code to use Outcome is not a bad place to start. Obviously enough, 189do choose a non-throwing policy when configuring `outcome` or `result` such as 190{{% api "all_narrow" %}} to guarantee that exceptions can never be thrown by Outcome 191(or use the convenience typedef for `result`, {{% api "unchecked<T, E = varies>" %}} which uses `policy::all_narrow`). 192 193 194## What kind of runtime performance impact will using Outcome in my code introduce? 195 196It is very hard to say anything definitive about performance impacts in codebases one 197has never seen. Each codebase is unique. However to come up with some form of measure, 198we timed traversing ten stack frames via each of the main mechanisms, including the 199"do nothing" (null) case. 200 201A stack frame is defined to be something called by the compiler whilst 202unwinding the stack between the point of return in the ultimate callee and the base 203caller, so for example ten stack allocated objects might be destructed, or ten levels 204of stack depth might be unwound. This is not a particularly realistic test, but it 205should at least give one an idea of the performance impact of returning Outcome's 206`result` or `outcome` over say returning a plain integer, or throwing an exception. 207 208The following figures are for Outcome v2.1.0 with GCC 7.4, clang 8.0 and Visual 209Studio 2017.9. Figures for newer Outcomes with newer compilers can be found at 210https://github.com/ned14/outcome/tree/develop/benchmark. 211 212### High end CPU: Intel Skylake x64 213 214This is a high end CPU with very significant ability to cache, predict, parallelise 215and execute out-of-order such that tight, repeated loops perform very well. It has 216a large μop cache able to wholly contain the test loop, meaning that these results 217are a **best case** performance. 218 219{{% figure src="/faq/results_skylake_log.png" title="Log graph comparing GCC 7.4, clang 8.0 and Visual Studio 2017.9 on x64, for exceptions-globally-disabled, ordinary and link-time-optimised build configurations." %}} 220 221As you can see, throwing and catching an exception is 222expensive on table-based exception handling implementations such as these, anywhere 223between 26,000 and 43,000 CPU cycles. And this is the *hot path* situation, this 224benchmark is a loop around hot cached code. If the tables are paged out onto storage, 225you are talking about **millions** of CPU cycles. 226 227Simple integer returns (i.e. do nothing null case) 228are always going to be the fastest as they do the least work, and that costs 80 to 90 229CPU cycles on this Intel Skylake CPU. 230 231Note that returning a `result<int, std::error_code>` with a "success (error code)" 232is no more than 5% added runtime overhead over returning a naked int on GCC and clang. On MSVC 233it costs an extra 20% or so, mainly due to poor code optimisation in the VS2017.9 compiler. Note that "success 234(experimental status code)" optimises much better, and has almost no overhead over a 235naked int. 236 237Returning a `result<int, std::error_code>` with a "failure (error code)" 238is less than 5% runtime overhead over returning a success on GCC, clang and MSVC. 239 240You might wonder what happens if type `E` has a non-trivial destructor, thus making the 241`result<T, E>` have a non-trivial destructor? We tested `E = std::exception_ptr` and 242found less than a 5% overhead to `E = std::error_code` for returning success. Returning a failure 243was obviously much slower at anywhere between 300 and 1,100 CPU cycles, due to the 244dynamic memory allocation and free of the exception ptr, plus at least two atomic operations per stack frame, but that is 245still two orders of magnitude better than throwing and catching an exception. 246 247We conclude that if failure is anything but extremely rare in your C++ codebase, 248using Outcome instead of throwing and catching exceptions ought to be quicker overall: 249 250- Experimental Outcome is statistically indistinguishable from the null case on this 251high end CPU, for both returning success and failure, on all compilers. 252- Standard Outcome is less than 5% 253worse than the null case for returning successes on GCC and clang, and less than 10% worse than 254the null case for returning failures on GCC and clang. 255- Standard Outcome optimises 256poorly on VS2017.9, indeed markedly worse than on previous point releases, so let's 257hope that Microsoft fix that soon. It currently has a less than 20% overhead on the null case. 258 259### Mid tier CPU: ARM Cortex A72 260 261This is a four year old mid tier CPU used in many high end mobile phones and tablets 262of its day, with good ability to cache, predict, parallelise 263and execute out-of-order such that tight, repeated loops perform very well. It has 264a μop cache able to wholly contain the test loop, meaning that these results 265are a **best case** performance. 266 267{{% figure src="/faq/results_arm_a72_log.png" title="Log graph comparing GCC 7.3 and clang 7.3 on ARM64, for exceptions-globally-disabled, ordinary and link-time-optimised build configurations." %}} 268 269This ARM chip is a very consistent performer -- null case, success, or failure, all take 270almost exactly the same CPU cycles. Choosing Outcome, in any configuration, makes no 271difference to not using Outcome at all. Throwing and catching a C++ exception costs 272about 90,000 CPU cycles, whereas the null case/Outcome costs about 130 - 140 CPU cycles. 273 274There is very little to say about this CPU, other than Outcome is zero overhead on it. The same 275applied to the ARM Cortex A15 incidentally, which I test cased extensively when 276deciding on the Outcome v2 design back after the first peer review. The v2 design 277was chosen partially because of such consistent performance on ARM. 278 279### Low end CPUs: Intel Silvermont x64 and ARM Cortex A53 280 281These are low end CPUs with a mostly or wholly in-order execution core. They have a small 282or no μop cache, meaning that the CPU must always decode the instruction stream. 283These results represent an execution environment more typical of CPUs two decades 284ago, back when table-based EH created a big performance win if you never threw 285an exception. 286 287{{% figure src="/faq/results_silvermont_log.png" title="Log graph comparing GCC 7.3 and clang 7.3 on x64, for exceptions-globally-disabled, ordinary and link-time-optimised build configurations." %}} 288{{% figure src="/faq/results_arm_a53_log.png" title="Log graph comparing GCC 7.3 and clang 7.3 on ARM64, for exceptions-globally-disabled, ordinary and link-time-optimised build configurations." %}} 289 290The first thing to mention is that clang generates very high performance code for 291in-order cores, far better than GCC. It is said that this is due to a very large investment by 292Apple in clang/LLVM for their devices sustained over many years. In any case, if you're 293targeting in-order CPUs, don't use GCC if you can use clang instead! 294 295For the null case, Silvermont and Cortex A53 are quite similar in terms of CPU clock cycles. Ditto 296for throwing and catching a C++ exception (approx 150,000 CPU cycles). However the Cortex 297A53 does far better with Outcome than Silvermont, a 15% versus 100% overhead for Standard 298Outcome, and a 4% versus 20% overhead for Experimental Outcome. 299 300Much of this large difference is in fact due to calling convention differences. x64 permits up to 8 bytes 301to be returned from functions by CPU register. `result<int>` consumes 24 bytes, so on x64 302the compiler writes the return value to the stack. However ARM64 permits up to 64 bytes 303to be returned in registers, so `result<int>` is returned via CPU registers on ARM64. 304 305On higher end CPUs, memory is read and written in cache lines (32 or 64 bytes), and 306reads and writes are coalesced and batched together by the out-of-order execution core. On these 307low end CPUs, memory is read and written sequentially per assembler instruction, 308so only one load or one store to L1 309cache can occur at a time. This makes writing the stack particularly slow on in-order 310CPUs. Memory operations which "disappear" on higher end CPUs take considerable time 311on low end CPUs. This particularly punishes Silvermont in a way which does not punish 312the Cortex A53, because of having to write multiple values to the stack to create the 31324 byte object to be returned. 314 315The conclusion to take away from this is that if you are targeting a low end CPU, 316table-based EH still delivers significant performance improvements for the success 317code path. Unless determinism in failure is critically important, you should not 318use Outcome on in-order execution CPUs. 319 320 321## Why is implicit default construction disabled? 322 323This was one of the more interesting points of discussion during the peer review of 324Outcome v1. v1 had a formal empty state. This came with many advantages, but it 325was not felt to be STL idiomatic as `std::optional<result<T>>` is what was meant, so 326v2 has eliminated any legal possibility of being empty. 327 328The `expected<T, E>` proposal of that time (May 2017) did permit default construction 329if its `T` type allowed default construction. This was specifically done to make 330`expected<T, E>` more useful in STL containers as one can say resize a vector without 331having to supply an `expected<T, E>` instance to fill the new items with. However 332there was some unease with that design choice, because it may cause programmers to 333use some type `T` whose default constructed state is overloaded with additional meaning, 334typically "to be filled" i.e. a de facto empty state via choosing a magic value. 335 336For the v2 redesign, the various arguments during the v1 review were considered. 337Unlike `expected<T, E>` which is intended to be a general purpose Either monad 338vocabulary type, Outcome's types are meant primarily for returning success or failure 339from functions. The API should therefore encourage the programmer to not overload 340the successful type with additional meaning of "to be filled" e.g. `result<std::optional<T>>`. 341The decision was therefore taken to disable *implicit* default construction, but 342still permit *explicit* default construction by making the programmer spell out their 343intention with extra typing. 344 345To therefore explicitly default construct a `result<T>` or `outcome<T>`, use one 346of these forms as is the most appropriate for the use case: 347 3481. Construct with just `in_place_type<T>` e.g. `result<T>(in_place_type<T>)`. 3492. Construct via `success()` e.g. `outcome<T>(success())`. 3503. Construct from a `void` form e.g. `result<T>(result<void>(in_place_type<void>))`. 351 352 353## How far away from the proposed `std::expected<T, E>` is Outcome's `checked<T, E>`? 354 355Not far, in fact after the first Boost.Outcome peer review in May 2017, Expected moved 356much closer to Outcome, and Outcome deliberately provides {{% api "checked<T, E = varies>" %}} 357as a semantic equivalent. 358 359Here are the remaining differences which represent the 360divergence of consensus opinion between the Boost peer review and WG21 on the proper 361design for this object: 362 3631. `checked<T, E>` has no default constructor. Expected has a default constructor if 364`T` has a default constructor. 3652. `checked<T, E>` uses the same constructor design as `std::variant<...>`. Expected 366uses the constructor design of `std::optional<T>`. 3673. `checked<T, E>` cannot be modified after construction except by assignment. 368Expected provides an `.emplace()` modifier. 3694. `checked<T, E>` permits implicit construction from both `T` and `E` when 370unambiguous. Expected permits implicit construction from `T` alone. 3715. `checked<T, E>` does not permit `T` and `E` to be the same, and becomes annoying 372to use if they are constructible into one another (implicit construction self-disables). 373Expected permits `T` and `E` to be the same. 3746. `checked<T, E>` throws `bad_result_access_with<E>` instead of Expected's 375`bad_expected_access<E>`. 3767. `checked<T, E>` models `std::variant<...>`. Expected models `std::optional<T>`. Thus: 377 - `checked<T, E>` does not provide `operator*()` nor `operator->` 378 - `checked<T, E>` `.error()` is wide (i.e. throws on no-value) like `.value()`. 379 Expected's `.error()` is narrow (UB on no-error). [`checked<T, E>` provides 380 `.assume_value()` and `.assume_error()` for narrow (UB causing) observers]. 3818. `checked<T, E>` uses `success<T>` and `failure<E>` type sugars for disambiguation. 382Expected uses `unexpected<E>` only. 3839. `checked<T, E>` requires `E` to be default constructible. 38410. `checked<T, E>` defaults `E` to `std::error_code` or `boost::system::error_code`. 385Expected does not default `E`. 386 387In fact, the two are sufficiently close in design that a highly conforming `expected<T, E>` 388can be implemented by wrapping up `checked<T, E>` with the differing functionality: 389 390{{% snippet "expected_implementation.cpp" "expected_implementation" %}} 391 392 393## Why doesn't Outcome duplicate `std::expected<T, E>`'s design? 394 395There are a number of reasons: 396 3971. Outcome is not aimed at the same audience as Expected. We target developers 398and users who would be happy to use Boost. Expected targets the standard library user. 399 4002. Outcome believes that the monadic use case isn't as important as Expected does. 401Specifically, we think that 99% of use of Expected in the real world will be to 402return failure from functions, and not as some sort of enhanced or "rich" Optional. 403Outcome therefore models a subset of Variant, whereas Expected models an extended Optional. 404 4053. Outcome believes that if you are thinking about using something like Outcome, 406then for you writing failure code will be in the same proportion as writing success code, 407and thus in Outcome writing for failure is exactly the same as writing for success. 408Expected assumes that success will be more common than failure, and makes you type 409more when writing for failure. 410 4114. Outcome goes to considerable effort to help the end user type fewer characters 412during use. This results in tighter, less verbose, more succinct code. The cost of this is a steeper 413learning curve and more complex mental model than when programming with Expected. 414 4155. Outcome has facilities to make easier interoperation between multiple third 416party libraries each using incommensurate Outcome (or Expected) configurations. Expected does 417not do any of this, but subsequent WG21 papers do propose various interoperation 418mechanisms, [one of which](https://wg21.link/P0786) Outcome implements so code using Expected will seamlessly 419interoperate with code using Outcome. 420 421 422## Is Outcome riddled with undefined behaviour for const, const-containing and reference-containing types? 423 424The short answer is not any more in C++ 20 and after, thanks to changes made to 425C++ 20 at the Belfast WG21 meeting in November 2019. 426 427The longer answer is that before C++ 20, use of placement 428new on types containing `const` member types where the resulting pointer was 429thrown away is undefined behaviour. As of the resolution of a national body 430comment, this is no longer the case, and now Outcome is free of this particular 431UB for C++ 20 onwards. 432 433This still affects C++ before 20, though no major compiler is affected. Still, 434if you wish to avoid UB, don't use `const` types within Outcome types (or any 435`optional<T>`, or `vector<T>` or any STL container type for that matter). 436 437### More detail 438 439Before the C++ 14 standard, placement new into storage which used to contain 440a const type was straight out always undefined behaviour, period. Thus all use of 441placement new within a `result<const_containing_type>`, or indeed an `optional<const_containing_type>`, is always 442undefined behaviour before C++ 14. From `[basic.life]` for the C++ 11 standard: 443 444> Creating a new object at the storage location that a const object with static, 445> thread, or automatic storage duration occupies or, at the storage location 446> that such a const object used to occupy before its lifetime ended results 447> in undefined behavior. 448 449This being excessively restrictive, from C++ 14 onwards, `[basic_life]` now states: 450 451> If, after the lifetime of an object has ended and before the storage which 452> the object occupied is reused or released, a new object is created at the 453> storage location which the original object occupied, a pointer that 454> pointed to the original object, a reference that referred to the original 455> object, or the name of the original object will automatically refer to the 456> new object and, once the lifetime of the new object has started, can be 457> used to manipulate the new object, if: 458> 459> — the storage for the new object exactly overlays the storage location which 460> the original object occupied, and 461> 462> — the new object is of the same type as the original object (ignoring the 463> top-level cv-qualifiers), and 464> 465> — the type of the original object is not const-qualified, and, if a class type, 466> does not contain any non-static data member whose type is const-qualified 467> or a reference type, and 468> 469> — neither the original object nor the new object is a potentially-overlapping 470> subobject 471 472Leaving aside my personal objections to giving placement new of non-const 473non-reference types magical pointer renaming powers, the upshot is that if 474you want defined behaviour for placement new of types containing const types 475or references, you must store the pointer returned by placement new, and use 476that pointer for all further reference to the newly created object. This 477obviously adds eight bytes of storage to a `result<const_containing_type>`, which is highly 478undesirable given all the care and attention paid to keeping it small. The alternative 479is to use {{% api "std::launder" %}}, which was added in C++ 17, to 'launder' 480the storage into which we placement new before each and every use of that 481storage. This forces the compiler to reload the object stored by placement 482new on every occasion, and not assume it can be constant propagated, which 483impacts codegen quality. 484 485As mentioned above, this issue (in so far as it applies to types containing 486user supplied `T` which might be `const`) has been resolved as of C++ 20 onwards, 487and it is extremely unlikely that any C++ compiler will act on any UB here in 488C++ 17 or 14 given how much of STL containers would break. 489