== `optional.hpp` https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/optional.hpp[`optional.hpp`] provides an implementation that mirrors https://en.cppreference.com/w/cpp/header/optional[``], but uses `tombstone_traits` instead of a `bool` variable. Here is a problem with `std::optional`: [source,cpp] ---- enum struct S1 : std::uint8_t { /* ... values ... */ }; static_assert(sizeof(S1) == 1); static_assert(sizeof(std::optional == 2); enum struct S2 : std::uint16_t { /* ... values ... */ }; static_assert(sizeof(S2) == 2); static_assert(sizeof(std::optional) == 4); enum struct S3 : std::uint32_t { /* ... values ... */ }; static_assert(sizeof(S3) == 4); static_assert(sizeof(std::optional) == 8); ---- `std::optional` basically stores a `bool` flag as well as the type. Because of size and alignment constraints, `std::optional` of a small type typically ends up being double the size of the type. Much of the time, we aren't using all the bits in the type - especially if it's an enumeration - so there is probably some sentinel value that we can treat as "invalid". AKA a "tombstone". This is where `tombstone_traits` come in: specializing `stdx::tombstone_traits` allows us to specify that sentinel value and avoid storing an extra `bool`. NOTE: The name "tombstone" arises from use in hash maps where it is used to signal a "dead" object. [source,cpp] ---- enum struct S : std::uint8_t { /* ... values ... */ }; template <> struct stdx::tombstone_traits { // "-1" is not a valid value constexpr auto operator()() const { return static_cast(std::uint8_t{0xffu}); } }; static_assert(sizeof(S) == 1); static_assert(sizeof(stdx::optional == 1); ---- To use `stdx::optional`, specialize `stdx::tombstone_traits` for the required type, giving it a call operator that returns the sentinel value. After that, `stdx::optional`'s interface mirrors that of https://en.cppreference.com/w/cpp/utility/optional[`std::optional`]. The C\+​+23 monadic operations on `std::optional` are available on `stdx::optional` with C++17. Why a call operator and not just a `static` value? To deal with move-only (and even non-movable) types. NOTE: Like `std::optional`, `stdx::optional` can be constructed with https://en.cppreference.com/w/cpp/utility/optional/nullopt_t[`std::nullopt`] or with https://en.cppreference.com/w/cpp/utility/in_place[`std::in_place`]. (`stdx` does not redefine these types.) NOTE: `stdx::optional` does not use exceptions. There is no `stdx::bad_optional_access`. If you access a disengaged `stdx::optional`, you will get the tombstone value! === Tombstone values Instead of specializing `stdx::tombstone_traits`, sometimes it's easier (especially for integral or enumeration types) to provide the tombstone value inline. We can do this with `stdx::tombstone_value`. [source,cpp] ---- auto o = stdx::optional>{}; ---- CAUTION: Don't specialize `tombstone_traits` for the builtin integral types - that's risky if the definition is seen more widely. Instead use a `stdx::tombstone_value` where needed. NOTE: The default `tombstone_traits` for floating-point types have https://en.cppreference.com/w/cpp/types/numeric_limits/infinity[infinity] as the tombstone value. At first thought, https://en.cppreference.com/w/cpp/numeric/math/isnan[NaN] is the obvious tombstone, but NaNs never compare equal to anything, not even themselves. For tuple-like types, if all of their component parts provide tombstone values, a tombstone value for them is synthesized: [source,cpp] ---- // S has a tombstone value, therefore so does std::tuple auto o = stdx::optional{std::tuple{S{42}, S{17}}}; ---- === Multi-argument `transform` `stdx::optional` provides one extra feature over `std::optional`: the ability to call `transform` with multiple arguments. https://en.cppreference.com/w/cpp/utility/optional/transform[C++23 `transform`] is a member function on `stdx::optional` too, but `stdx::transform` exists also as a free function on `stdx::optional`. [source,cpp] ---- // S is a struct with tombstone_traits that contains an integer value auto opt1 = stdx::optional{17}; auto opt2 = stdx::optional{42}; auto opt_sum = transform( [](S const &x, S const &y) { return S{x.value + y.value}; }, opt1, opt2); ---- This flavor of `transform` returns the result of the function only if all of its `stdx::optional` arguments are engaged. If any one is not, a disengaged `stdx::optional` is returned. === Unpacking `transform` and `and_then` When the contained `value_type` supports the tuple protocol with `apply`, `transform` (or `and_then`) can unpack it to pass arguments to a function: [source,cpp] ---- auto opt1 = stdx::optional{std::tuple{S{42}, S{17}}}; auto opt_sum = transform( [](S const &x, S const &y) { return S{x.value + y.value}; }, opt1); // result is stdx::optional{S{59}} ----