[libc++] Implement P3798R1: The unexpected in std::expected
Project: llvm/llvm-project | Language: C++ | Concepts: api-design, backwards-compatibility, testing-strategy | Difficulty: mid
Link: https://github.com/llvm/llvm-project/pull/219236
The problem
C++26 (and now C++29) introduces a new observer, has_error(), to std::expected, as specified by proposal P3798R1. This function allows users to directly check if an expected object contains an error, improving code clarity and intent. The libc++ implementation was missing this function and the associated feature test macro updates.
Why it's non-trivial
- The new function must only be available in C++29 and later, preserving backwards compatibility.
- The feature test macro (
__cpp_lib_expected) must be bumped for C++29, but not for previous standards. - Tests and documentation must be updated to reflect the new API and macro values.
- All changes must integrate smoothly with existing code, tests, and versioning mechanisms.
The approach
1. Implementing has_error() in the library
The new observer is conditionally added to both the primary and void-specialized expected classes, only for C++29 and later:
+# if _LIBCPP_STD_VER >= 29
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr bool has_error() const noexcept { return !this->has_value(); }
+# endif
This snippet adds a has_error() member function that returns the negation of has_value(), ensuring correct semantics. The #if ensures this is only present in C++29 and up.
2. Updating the feature test macro for C++29
The value of __cpp_lib_expected is bumped to 202606L for C++29, while retaining the old value for previous standards:
-__cpp_lib_expected 202211L <expected>
+__cpp_lib_expected 202606L <expected>
+ 202211L // C++23
And in the version header:
#if _LIBCPP_STD_VER >= 29
+# undef __cpp_lib_expected
+# define __cpp_lib_expected 202606L
This ensures that code can detect the presence of the new observer via the macro value.
3. Updating documentation and status tables
The feature is marked as implemented in release notes and status tables:
+- P3798R1: The unexpected in ``std::expected`` (`Github <https://llvm.org/PR204394>`__)
-"`P3798R1 <https://wg21.link/P3798R1>`__","The unexpected in ``std::expected``","2026-06 (Brno)","","","`#204394 <https://github.com/llvm/llvm-project/issues/204394>`__",""
+"`P3798R1 <https://wg21.link/P3798R1>`__","The unexpected in ``std::expected``","2026-06 (Brno)","|Complete|","29","`#204394 <https://github.com/llvm/llvm-project/issues/204394>`__",""
This records the implementation in project documentation.
4. Adding and updating tests
New tests are added to verify has_error() for both value and void specializations:
+// constexpr bool has_error() const noexcept;
+const std::expected<int, int> e(std::unexpect, 5);
+static_assert(noexcept(e.has_error()));
+std::same_as<bool> decltype(auto) has_err = e.has_error();
+assert(has_err);
Tests for the feature test macro are also updated:
-# if __cpp_lib_expected != 202211L
-# error "__cpp_lib_expected should have the value 202211L in c++29"
+# if __cpp_lib_expected != 202606L
+# error "__cpp_lib_expected should have the value 202606L in c++29"
This ensures the macro value is correct in C++29.
Tests for the nodiscard attribute are updated to include has_error():
- exp.has_value(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+ exp.has_value(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+ exp.has_error(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
5. Updating feature test macro generation script
The macro generator is updated so future macro regeneration will include the new value:
- # "c++29": 202606 # P3798R1 The unexpected in std::expected
+ "c++29": 202606,
From the review thread
No notable review discussion on this PR.
Takeaways
- When adding new API to a standard library, always conditionally enable it based on the standard version to maintain backwards compatibility.
- Feature test macros must be updated whenever new features are added, so that users and other libraries can detect their availability.
- Comprehensive testing (including for API presence, macro values, and attributes) is essential to ensure correct integration and avoid regressions.