[libc-commits] [libc] [libc] refactor expected to correctly destruct payload (PR #195873)

Schrodinger ZHU Yifan via libc-commits libc-commits at lists.llvm.org
Tue May 5 08:34:17 PDT 2026


https://github.com/SchrodingerZhu created https://github.com/llvm/llvm-project/pull/195873

None

>From 0eebcbe2d43cce7120e1d81bd10af562c68fe889 Mon Sep 17 00:00:00 2001
From: Schrodinger ZHU Yifan <yifanzhu at rochester.edu>
Date: Tue, 5 May 2026 11:33:52 -0400
Subject: [PATCH] [libc] refactor expected to correctly destruct payload

---
 libc/src/__support/CPP/CMakeLists.txt         |  3 +
 libc/src/__support/CPP/expected.h             | 93 +++++++++++++++----
 libc/test/src/__support/CPP/CMakeLists.txt    | 10 ++
 libc/test/src/__support/CPP/expected_test.cpp | 65 +++++++++++++
 4 files changed, 153 insertions(+), 18 deletions(-)
 create mode 100644 libc/test/src/__support/CPP/expected_test.cpp

diff --git a/libc/src/__support/CPP/CMakeLists.txt b/libc/src/__support/CPP/CMakeLists.txt
index bdfbc6151c773..f6c9ef5d0b20c 100644
--- a/libc/src/__support/CPP/CMakeLists.txt
+++ b/libc/src/__support/CPP/CMakeLists.txt
@@ -197,6 +197,9 @@ add_header_library(
   expected
   HDRS
     expected.h
+  DEPENDS
+    .type_traits
+    .utility
 )
 
 add_object_library(
diff --git a/libc/src/__support/CPP/expected.h b/libc/src/__support/CPP/expected.h
index 8a93091f0ebfb..80512b3c4faa7 100644
--- a/libc/src/__support/CPP/expected.h
+++ b/libc/src/__support/CPP/expected.h
@@ -9,6 +9,8 @@
 #ifndef LLVM_LIBC_SRC___SUPPORT_CPP_EXPECTED_H
 #define LLVM_LIBC_SRC___SUPPORT_CPP_EXPECTED_H
 
+#include "src/__support/CPP/type_traits.h"
+#include "src/__support/CPP/utility.h"
 #include "src/__support/macros/attributes.h"
 #include "src/__support/macros/config.h"
 
@@ -21,37 +23,92 @@ template <class T> class unexpected {
   T value;
 
 public:
-  LIBC_INLINE constexpr explicit unexpected(T value) : value(value) {}
-  LIBC_INLINE constexpr T error() { return value; }
+  LIBC_INLINE constexpr explicit unexpected(T value)
+      : value(cpp::move(value)) {}
+
+  LIBC_INLINE constexpr T &error() & { return value; }
+  LIBC_INLINE constexpr const T &error() const & { return value; }
+  LIBC_INLINE constexpr T &&error() && { return cpp::move(value); }
 };
 
 template <class T> explicit unexpected(T) -> unexpected<T>;
 
-template <class T, class E> class expected {
+struct unexpect_t {
+  LIBC_INLINE constexpr explicit unexpect_t() = default;
+};
+
+LIBC_INLINE_VAR constexpr unexpect_t unexpect{};
+
+template <class T, class E,
+          bool =
+              is_trivially_destructible_v<T> && is_trivially_destructible_v<E>>
+struct expected_storage;
+
+// Trivial case: no destructor declared.
+template <class T, class E> struct expected_storage<T, E, true> {
+  union {
+    T val;
+    E err;
+  };
+
+  bool exp;
+
+  template <typename... Args>
+  LIBC_INLINE constexpr explicit expected_storage(in_place_t, Args &&...args)
+      : val(cpp::forward<Args>(args)...), exp(true) {}
+
+  template <typename... Args>
+  LIBC_INLINE constexpr explicit expected_storage(unexpect_t, Args &&...args)
+      : err(cpp::forward<Args>(args)...), exp(false) {}
+};
+
+// Non-trivial case: destructor destroys the active member.
+template <class T, class E> struct expected_storage<T, E, false> {
   union {
-    T exp;
-    E unexp;
+    T val;
+    E err;
   };
-  bool is_expected;
+
+  bool exp;
+
+  template <typename... Args>
+  LIBC_INLINE constexpr explicit expected_storage(in_place_t, Args &&...args)
+      : val(cpp::forward<Args>(args)...), exp(true) {}
+
+  template <typename... Args>
+  LIBC_INLINE constexpr explicit expected_storage(unexpect_t, Args &&...args)
+      : err(cpp::forward<Args>(args)...), exp(false) {}
+
+  LIBC_INLINE ~expected_storage() {
+    if (exp)
+      val.~T();
+    else
+      err.~E();
+  }
+};
+
+template <class T, class E> class expected : private expected_storage<T, E> {
+  using Base = expected_storage<T, E>;
 
 public:
-  LIBC_INLINE constexpr expected(T exp) : exp(exp), is_expected(true) {}
+  LIBC_INLINE constexpr expected(const T &val) : Base(in_place, val) {}
+  LIBC_INLINE constexpr expected(T &&val) : Base(in_place, cpp::move(val)) {}
   LIBC_INLINE constexpr expected(unexpected<E> unexp)
-      : unexp(unexp.error()), is_expected(false) {}
+      : Base(unexpect, cpp::move(unexp).error()) {}
 
-  LIBC_INLINE constexpr bool has_value() const { return is_expected; }
+  LIBC_INLINE constexpr bool has_value() const { return this->exp; }
 
-  LIBC_INLINE constexpr T &value() { return exp; }
-  LIBC_INLINE constexpr E &error() { return unexp; }
-  LIBC_INLINE constexpr const T &value() const { return exp; }
-  LIBC_INLINE constexpr const E &error() const { return unexp; }
+  LIBC_INLINE constexpr T &value() { return this->val; }
+  LIBC_INLINE constexpr E &error() { return this->err; }
+  LIBC_INLINE constexpr const T &value() const { return this->val; }
+  LIBC_INLINE constexpr const E &error() const { return this->err; }
 
-  LIBC_INLINE constexpr operator bool() const { return is_expected; }
+  LIBC_INLINE constexpr operator bool() const { return this->exp; }
 
-  LIBC_INLINE constexpr T &operator*() { return exp; }
-  LIBC_INLINE constexpr const T &operator*() const { return exp; }
-  LIBC_INLINE constexpr T *operator->() { return &exp; }
-  LIBC_INLINE constexpr const T *operator->() const { return &exp; }
+  LIBC_INLINE constexpr T &operator*() { return this->val; }
+  LIBC_INLINE constexpr const T &operator*() const { return this->val; }
+  LIBC_INLINE constexpr T *operator->() { return &this->val; }
+  LIBC_INLINE constexpr const T *operator->() const { return &this->val; }
 };
 
 } // namespace cpp
diff --git a/libc/test/src/__support/CPP/CMakeLists.txt b/libc/test/src/__support/CPP/CMakeLists.txt
index 430cd7b136c69..962c95a733048 100644
--- a/libc/test/src/__support/CPP/CMakeLists.txt
+++ b/libc/test/src/__support/CPP/CMakeLists.txt
@@ -130,6 +130,16 @@ add_libc_test(
     libc.src.__support.CPP.optional
 )
 
+add_libc_test(
+  expected_test
+  SUITE
+    libc-cpp-utils-tests
+  SRCS
+    expected_test.cpp
+  DEPENDS
+    libc.src.__support.CPP.expected
+)
+
 add_libc_test(
   tuple_test
   SUITE
diff --git a/libc/test/src/__support/CPP/expected_test.cpp b/libc/test/src/__support/CPP/expected_test.cpp
new file mode 100644
index 0000000000000..c73f6f945892b
--- /dev/null
+++ b/libc/test/src/__support/CPP/expected_test.cpp
@@ -0,0 +1,65 @@
+//===-- Unittests for Expected --------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/__support/CPP/expected.h"
+#include "src/__support/CPP/type_traits.h"
+#include "test/UnitTest/Test.h"
+
+using LIBC_NAMESPACE::cpp::expected;
+using LIBC_NAMESPACE::cpp::unexpected;
+
+class MoveOnly {
+  int *destroyed;
+
+public:
+  explicit MoveOnly(int *destroyed) : destroyed(destroyed) {}
+
+  MoveOnly(const MoveOnly &) = delete;
+  MoveOnly &operator=(const MoveOnly &) = delete;
+
+  MoveOnly(MoveOnly &&other) : destroyed(other.destroyed) {
+    other.destroyed = nullptr;
+  }
+
+  ~MoveOnly() {
+    if (destroyed)
+      ++*destroyed;
+  }
+};
+
+static_assert(
+    LIBC_NAMESPACE::cpp::is_trivially_destructible_v<expected<int, int>>,
+    "expected should be trivially destructible for trivial payloads");
+static_assert(!LIBC_NAMESPACE::cpp::is_trivially_destructible_v<
+                  expected<MoveOnly, int>>,
+              "expected should be nontrivially destructible for nontrivial "
+              "value payloads");
+static_assert(!LIBC_NAMESPACE::cpp::is_trivially_destructible_v<
+                  expected<int, MoveOnly>>,
+              "expected should be nontrivially destructible for nontrivial "
+              "error payloads");
+
+TEST(LlvmLibcExpectedTest, DestroysNonTrivialValue) {
+  int destroyed = 0;
+  {
+    expected<MoveOnly, int> value{MoveOnly(&destroyed)};
+    ASSERT_TRUE(value.has_value());
+    ASSERT_TRUE(static_cast<bool>(value));
+  }
+  ASSERT_EQ(destroyed, 1);
+}
+
+TEST(LlvmLibcExpectedTest, DestroysNonTrivialError) {
+  int destroyed = 0;
+  {
+    expected<int, MoveOnly> error{unexpected<MoveOnly>{MoveOnly(&destroyed)}};
+    ASSERT_FALSE(error.has_value());
+    ASSERT_FALSE(static_cast<bool>(error));
+  }
+  ASSERT_EQ(destroyed, 1);
+}



More information about the libc-commits mailing list