Skip to content

Conversation

@H-G-Hristov
Copy link
Contributor

[[nodiscard]] should be applied to functions where discarding the return value is most likely a correctness issue.

@H-G-Hristov H-G-Hristov force-pushed the hgh/libcxx/nodiscard-to-optional branch 6 times, most recently from 416db44 to 40cf697 Compare December 1, 2025 06:25
@Zingam
Copy link
Contributor

Zingam commented Dec 1, 2025

@smallp-o-p Would you please review?

`[[nodiscard]]` should be applied to functions where discarding the return value is most likely a correctness issue.

- https://libcxx.llvm.org/CodingGuidelines.html
- https://wg21.link/optional
@H-G-Hristov H-G-Hristov force-pushed the hgh/libcxx/nodiscard-to-optional branch from 40cf697 to ff99ffa Compare December 1, 2025 07:46
// Get the key function ~bad_optional_access() into the dylib
~bad_optional_access() _NOEXCEPT override;
const char* what() const _NOEXCEPT override;
[[__nodiscard__]] const char* what() const _NOEXCEPT override;
Copy link
Contributor

@frederick-vs-ja frederick-vs-ja Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No change requested. I very wonder why we decided to expose bad_optional_access to old modes. @EricWF

H-G-Hristov and others added 2 commits December 1, 2025 20:28
@H-G-Hristov H-G-Hristov force-pushed the hgh/libcxx/nodiscard-to-optional branch from fcd651d to 6e305c1 Compare December 2, 2025 09:33
@H-G-Hristov H-G-Hristov marked this pull request as ready for review December 3, 2025 05:51
@H-G-Hristov H-G-Hristov requested a review from a team as a code owner December 3, 2025 05:51
@llvmbot llvmbot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Dec 3, 2025
@llvmbot
Copy link
Member

llvmbot commented Dec 3, 2025

@llvm/pr-subscribers-libcxx

Author: Hristo Hristov (H-G-Hristov)

Changes

[[nodiscard]] should be applied to functions where discarding the return value is most likely a correctness issue.


Patch is 24.83 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/170045.diff

5 Files Affected:

  • (modified) libcxx/include/optional (+35-34)
  • (added) libcxx/test/libcxx/utilities/optional/nodiscard.verify.cpp (+138)
  • (modified) libcxx/test/std/utilities/optional/optional.monadic/and_then.pass.cpp (+8-8)
  • (modified) libcxx/test/std/utilities/optional/optional.monadic/or_else.pass.cpp (+4-4)
  • (modified) libcxx/test/std/utilities/optional/optional.monadic/transform.pass.cpp (+10-10)
diff --git a/libcxx/include/optional b/libcxx/include/optional
index 23b21364b1a79..50ff0232b5c22 100644
--- a/libcxx/include/optional
+++ b/libcxx/include/optional
@@ -275,7 +275,7 @@ public:
   _LIBCPP_HIDE_FROM_ABI bad_optional_access& operator=(const bad_optional_access&) _NOEXCEPT = default;
   // Get the key function ~bad_optional_access() into the dylib
   ~bad_optional_access() _NOEXCEPT override;
-  const char* what() const _NOEXCEPT override;
+  [[__nodiscard__]] const char* what() const _NOEXCEPT override;
 };
 
 } // namespace std
@@ -378,7 +378,7 @@ struct __optional_storage_base : __optional_destruct_base<_Tp> {
   using value_type             = _Tp;
   using __base::__base;
 
-  _LIBCPP_HIDE_FROM_ABI constexpr bool has_value() const noexcept { return this->__engaged_; }
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr bool has_value() const noexcept { return this->__engaged_; }
 
   _LIBCPP_HIDE_FROM_ABI constexpr value_type& __get() & noexcept { return this->__val_; }
   _LIBCPP_HIDE_FROM_ABI constexpr const value_type& __get() const& noexcept { return this->__val_; }
@@ -440,7 +440,7 @@ struct __optional_storage_base<_Tp, true> {
 
   _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void reset() noexcept { __value_ = nullptr; }
 
-  _LIBCPP_HIDE_FROM_ABI constexpr bool has_value() const noexcept { return __value_ != nullptr; }
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr bool has_value() const noexcept { return __value_ != nullptr; }
 
   _LIBCPP_HIDE_FROM_ABI constexpr value_type& __get() const& noexcept { return *__value_; }
 
@@ -634,7 +634,7 @@ public:
 #      endif
 
   // [optional.iterators], iterator support
-  _LIBCPP_HIDE_FROM_ABI constexpr iterator begin() noexcept {
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr iterator begin() noexcept {
     auto& __derived_self = static_cast<optional<_Tp>&>(*this);
     auto __ptr           = [&__derived_self]() {
       if constexpr (is_lvalue_reference_v<_Tp>) {
@@ -653,7 +653,7 @@ public:
 #      endif
   }
 
-  _LIBCPP_HIDE_FROM_ABI constexpr const_iterator begin() const noexcept {
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr const_iterator begin() const noexcept {
     auto& __derived_self = static_cast<const optional<_Tp>&>(*this);
     auto* __ptr          = [&__derived_self]() {
       if constexpr (is_lvalue_reference_v<_Tp>) {
@@ -672,10 +672,10 @@ public:
 #      endif
   }
 
-  _LIBCPP_HIDE_FROM_ABI constexpr iterator end() noexcept {
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr iterator end() noexcept {
     return begin() + (static_cast<optional<_Tp>&>(*this).has_value() ? 1 : 0);
   }
-  _LIBCPP_HIDE_FROM_ABI constexpr const_iterator end() const noexcept {
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr const_iterator end() const noexcept {
     return begin() + (static_cast<const optional<_Tp>&>(*this).has_value() ? 1 : 0);
   }
 #    endif
@@ -946,22 +946,22 @@ public:
     return std::addressof(this->__get());
   }
 
-  _LIBCPP_HIDE_FROM_ABI constexpr const _Tp& operator*() const& noexcept {
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr const _Tp& operator*() const& noexcept {
     _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(this->has_value(), "optional operator* called on a disengaged value");
     return this->__get();
   }
 
-  _LIBCPP_HIDE_FROM_ABI constexpr _Tp& operator*() & noexcept {
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr _Tp& operator*() & noexcept {
     _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(this->has_value(), "optional operator* called on a disengaged value");
     return this->__get();
   }
 
-  _LIBCPP_HIDE_FROM_ABI constexpr _Tp&& operator*() && noexcept {
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr _Tp&& operator*() && noexcept {
     _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(this->has_value(), "optional operator* called on a disengaged value");
     return std::move(this->__get());
   }
 
-  _LIBCPP_HIDE_FROM_ABI constexpr const _Tp&& operator*() const&& noexcept {
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr const _Tp&& operator*() const&& noexcept {
     _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(this->has_value(), "optional operator* called on a disengaged value");
     return std::move(this->__get());
   }
@@ -971,25 +971,25 @@ public:
   using __base::__get;
   using __base::has_value;
 
-  _LIBCPP_HIDE_FROM_ABI constexpr _Tp const& value() const& {
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr _Tp const& value() const& {
     if (!this->has_value())
       std::__throw_bad_optional_access();
     return this->__get();
   }
 
-  _LIBCPP_HIDE_FROM_ABI constexpr _Tp& value() & {
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr _Tp& value() & {
     if (!this->has_value())
       std::__throw_bad_optional_access();
     return this->__get();
   }
 
-  _LIBCPP_HIDE_FROM_ABI constexpr _Tp&& value() && {
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr _Tp&& value() && {
     if (!this->has_value())
       std::__throw_bad_optional_access();
     return std::move(this->__get());
   }
 
-  _LIBCPP_HIDE_FROM_ABI constexpr _Tp const&& value() const&& {
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr _Tp const&& value() const&& {
     if (!this->has_value())
       std::__throw_bad_optional_access();
     return std::move(this->__get());
@@ -1000,7 +1000,7 @@ public:
     requires(!(is_lvalue_reference_v<_Tp> && is_function_v<__libcpp_remove_reference_t<_Tp>>) &&
              !(is_lvalue_reference_v<_Tp> && is_array_v<__libcpp_remove_reference_t<_Tp>>))
 #    endif
-  _LIBCPP_HIDE_FROM_ABI constexpr _Tp value_or(_Up&& __v) const& {
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr _Tp value_or(_Up&& __v) const& {
     static_assert(is_copy_constructible_v<_Tp>, "optional<T>::value_or: T must be copy constructible");
     static_assert(is_convertible_v<_Up, _Tp>, "optional<T>::value_or: U must be convertible to T");
     return this->has_value() ? this->__get() : static_cast<_Tp>(std::forward<_Up>(__v));
@@ -1010,7 +1010,7 @@ public:
 #    if _LIBCPP_STD_VER >= 26
     requires(!is_lvalue_reference_v<_Tp>)
 #    endif
-  _LIBCPP_HIDE_FROM_ABI constexpr _Tp value_or(_Up&& __v) && {
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr _Tp value_or(_Up&& __v) && {
     static_assert(is_move_constructible_v<_Tp>, "optional<T>::value_or: T must be move constructible");
     static_assert(is_convertible_v<_Up, _Tp>, "optional<T>::value_or: U must be convertible to T");
     return this->has_value() ? std::move(this->__get()) : static_cast<_Tp>(std::forward<_Up>(__v));
@@ -1020,7 +1020,7 @@ public:
   template <class _Up = remove_cv_t<_Tp>>
     requires(is_lvalue_reference_v<_Tp> &&
              !(is_function_v<__libcpp_remove_reference_t<_Tp>> || is_array_v<__libcpp_remove_reference_t<_Tp>>))
-  _LIBCPP_HIDE_FROM_ABI constexpr _Tp value_or(_Up&& __v) && {
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr _Tp value_or(_Up&& __v) && {
     static_assert(is_move_constructible_v<_Tp>, "optional<T>::value_or: T must be move constructible");
     static_assert(is_convertible_v<_Up, _Tp>, "optional<T>::value_or: U must be convertible to T");
     return this->has_value() ? this->__get() : static_cast<_Tp>(std::forward<_Up>(__v));
@@ -1029,7 +1029,7 @@ public:
 
 #    if _LIBCPP_STD_VER >= 23
   template <class _Func>
-  _LIBCPP_HIDE_FROM_ABI constexpr auto and_then(_Func&& __f) & {
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto and_then(_Func&& __f) & {
     using _Up = invoke_result_t<_Func, _Tp&>;
     static_assert(__is_std_optional<remove_cvref_t<_Up>>::value,
                   "Result of f(value()) must be a specialization of std::optional");
@@ -1039,7 +1039,7 @@ public:
   }
 
   template <class _Func>
-  _LIBCPP_HIDE_FROM_ABI constexpr auto and_then(_Func&& __f) const& {
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto and_then(_Func&& __f) const& {
     using _Up = invoke_result_t<_Func, const _Tp&>;
     static_assert(__is_std_optional<remove_cvref_t<_Up>>::value,
                   "Result of f(value()) must be a specialization of std::optional");
@@ -1049,7 +1049,7 @@ public:
   }
 
   template <class _Func>
-  _LIBCPP_HIDE_FROM_ABI constexpr auto and_then(_Func&& __f) && {
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto and_then(_Func&& __f) && {
     using _Up = invoke_result_t<_Func, _Tp&&>;
     static_assert(__is_std_optional<remove_cvref_t<_Up>>::value,
                   "Result of f(std::move(value())) must be a specialization of std::optional");
@@ -1059,7 +1059,7 @@ public:
   }
 
   template <class _Func>
-  _LIBCPP_HIDE_FROM_ABI constexpr auto and_then(_Func&& __f) const&& {
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto and_then(_Func&& __f) const&& {
     using _Up = invoke_result_t<_Func, const _Tp&&>;
     static_assert(__is_std_optional<remove_cvref_t<_Up>>::value,
                   "Result of f(std::move(value())) must be a specialization of std::optional");
@@ -1069,7 +1069,7 @@ public:
   }
 
   template <class _Func>
-  _LIBCPP_HIDE_FROM_ABI constexpr auto transform(_Func&& __f) & {
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto transform(_Func&& __f) & {
     using _Up = remove_cv_t<invoke_result_t<_Func, _Tp&>>;
     static_assert(!is_array_v<_Up>, "Result of f(value()) should not be an Array");
     static_assert(!is_same_v<_Up, in_place_t>, "Result of f(value()) should not be std::in_place_t");
@@ -1081,7 +1081,7 @@ public:
   }
 
   template <class _Func>
-  _LIBCPP_HIDE_FROM_ABI constexpr auto transform(_Func&& __f) const& {
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto transform(_Func&& __f) const& {
     using _Up = remove_cv_t<invoke_result_t<_Func, const _Tp&>>;
     static_assert(!is_array_v<_Up>, "Result of f(value()) should not be an Array");
     static_assert(!is_same_v<_Up, in_place_t>, "Result of f(value()) should not be std::in_place_t");
@@ -1093,7 +1093,7 @@ public:
   }
 
   template <class _Func>
-  _LIBCPP_HIDE_FROM_ABI constexpr auto transform(_Func&& __f) && {
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto transform(_Func&& __f) && {
     using _Up = remove_cv_t<invoke_result_t<_Func, _Tp&&>>;
     static_assert(!is_array_v<_Up>, "Result of f(std::move(value())) should not be an Array");
     static_assert(!is_same_v<_Up, in_place_t>, "Result of f(std::move(value())) should not be std::in_place_t");
@@ -1105,7 +1105,7 @@ public:
   }
 
   template <class _Func>
-  _LIBCPP_HIDE_FROM_ABI constexpr auto transform(_Func&& __f) const&& {
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto transform(_Func&& __f) const&& {
     using _Up = remove_cvref_t<invoke_result_t<_Func, const _Tp&&>>;
     static_assert(!is_array_v<_Up>, "Result of f(std::move(value())) should not be an Array");
     static_assert(!is_same_v<_Up, in_place_t>, "Result of f(std::move(value())) should not be std::in_place_t");
@@ -1117,7 +1117,7 @@ public:
   }
 
   template <invocable _Func>
-  _LIBCPP_HIDE_FROM_ABI constexpr optional or_else(_Func&& __f) const&
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr optional or_else(_Func&& __f) const&
     requires is_copy_constructible_v<_Tp>
   {
     static_assert(is_same_v<remove_cvref_t<invoke_result_t<_Func>>, optional>,
@@ -1128,7 +1128,7 @@ public:
   }
 
   template <invocable _Func>
-  _LIBCPP_HIDE_FROM_ABI constexpr optional or_else(_Func&& __f) &&
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr optional or_else(_Func&& __f) &&
     requires is_move_constructible_v<_Tp>
   {
     static_assert(is_same_v<remove_cvref_t<invoke_result_t<_Func>>, optional>,
@@ -1426,8 +1426,8 @@ operator<=>(const optional<_Tp>& __x, const _Up& __v) {
 #    endif // _LIBCPP_STD_VER >= 20
 
 template <class _Tp, enable_if_t< is_move_constructible_v<_Tp> && is_swappable_v<_Tp>, int> = 0>
-inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void
-swap(optional<_Tp>& __x, optional<_Tp>& __y) noexcept(noexcept(__x.swap(__y))) {
+inline _LIBCPP_HIDE_FROM_ABI
+_LIBCPP_CONSTEXPR_SINCE_CXX20 void swap(optional<_Tp>& __x, optional<_Tp>& __y) noexcept(noexcept(__x.swap(__y))) {
   __x.swap(__y);
 }
 
@@ -1440,17 +1440,18 @@ template <
     __make_optional_barrier_tag = __make_optional_barrier_tag{},
 #    endif
     class _Tp>
-_LIBCPP_HIDE_FROM_ABI constexpr optional<decay_t<_Tp>> make_optional(_Tp&& __v) {
+[[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr optional<decay_t<_Tp>> make_optional(_Tp&& __v) {
   return optional<decay_t<_Tp>>(std::forward<_Tp>(__v));
 }
 
 template <class _Tp, class... _Args>
-_LIBCPP_HIDE_FROM_ABI constexpr optional<_Tp> make_optional(_Args&&... __args) {
+[[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr optional<_Tp> make_optional(_Args&&... __args) {
   return optional<_Tp>(in_place, std::forward<_Args>(__args)...);
 }
 
 template <class _Tp, class _Up, class... _Args>
-_LIBCPP_HIDE_FROM_ABI constexpr optional<_Tp> make_optional(initializer_list<_Up> __il, _Args&&... __args) {
+[[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr optional<_Tp>
+make_optional(initializer_list<_Up> __il, _Args&&... __args) {
   return optional<_Tp>(in_place, __il, std::forward<_Args>(__args)...);
 }
 
@@ -1461,7 +1462,7 @@ struct hash< __enable_hash_helper<optional<_Tp>, remove_const_t<_Tp>> > {
   _LIBCPP_DEPRECATED_IN_CXX17 typedef size_t result_type;
 #    endif
 
-  _LIBCPP_HIDE_FROM_ABI size_t operator()(const optional<_Tp>& __opt) const {
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI size_t operator()(const optional<_Tp>& __opt) const {
     return static_cast<bool>(__opt) ? hash<remove_const_t<_Tp>>()(*__opt) : 0;
   }
 };
diff --git a/libcxx/test/libcxx/utilities/optional/nodiscard.verify.cpp b/libcxx/test/libcxx/utilities/optional/nodiscard.verify.cpp
new file mode 100644
index 0000000000000..c49546cfdf4ad
--- /dev/null
+++ b/libcxx/test/libcxx/utilities/optional/nodiscard.verify.cpp
@@ -0,0 +1,138 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++17
+
+// <optional>
+
+// Check that functions are marked [[nodiscard]]
+
+#include <string>
+#include <optional>
+#include <utility>
+
+#include "test_macros.h"
+
+void test() {
+  // [optional.bad.access]
+
+  std::bad_optional_access ex;
+
+  ex.what(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+
+  // [optional.optional]
+
+  std::optional<int> opt;
+  const std::optional<int> cOpt;
+
+  opt.has_value(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+
+#if TEST_STD_VER >= 26
+  opt.begin();  // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+  cOpt.begin(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+  opt.end();    // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+  cOpt.end();   // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+#endif
+
+  *opt;             // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+  *cOpt;            // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+  *std::move(opt);  // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+  *std::move(cOpt); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+
+  opt.value();             // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+  cOpt.value();            // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+  std::move(opt).value();  // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+  std::move(cOpt).value(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+
+  // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+  opt.value_or(94);
+  // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+  cOpt.value_or(94);
+  // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+  std::move(opt).value_or(94);
+  // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+  std::move(cOpt).value_or(94);
+
+#if TEST_STD_VER >= 23
+  // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+  opt.and_then([](int&) { return std::optional<int>{82}; });
+  // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+  cOpt.and_then([](const int&) { return std::optional<int>{82}; });
+  // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+  std::move(opt).and_then([](int&&) { return std::optional<int>{82}; });
+  // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+  std::move(cOpt).and_then([](const int&&) { return std::optional<int>{82}; });
+
+  // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+  opt.transform([](int&) { return std::optional<int>{94}; });
+  // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+  cOpt.transform([](const int&) { return std::optional<int>{94}; });
+  // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+  std::move(opt).transform([](int&&) { return std::optional<int>{94}; });
+  // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+  std::move(cOpt).transform([](const int&&) { return std::optional<int>{94}; });
+
+  // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+  opt.or_else([] { return std::optional<int>{82}; });
+  // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+  std::move(opt).or_else([] { return std::optional<int>{82}; });
+#endif // TEST_STD_VER >= 23
+
+  // [optional.optional.ref]
+
+#if TEST_STD_VER >= 26
+  int z = 94;
+  std::optional<int&> optRef{z};
+  const std::optional<int&> cOptRef{z};
+
+  optRef.has_value(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+
+  optRef.begin(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+  optRef.end();   // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+
+  *optRef;  // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+  *cOptRef; // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+
+  optRef.value(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+
+  // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+  std::move(optRef).value_or(z);
+  // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+  cOptRef.value_or(z);
+
+  // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+  optRef.and_then([](int&) { return std::optional<int>{94}; });
+  // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+  cOptRef.and_then([](int&) { return std::optional<int>{94}; });
+
+  // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+  optRef.transform([](int&) { return std::optional<int>{82}; });
+  // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+  cOptRef.transform([](int&) { return std::optional<int>{82}; });
+
+  // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+  optRef.or_else([] { return std::optional<int&>{}; });
+  // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+  std::move(optRef).or_else([] {...
[truncated]

@Zingam Zingam merged commit 63ea393 into llvm:main Dec 12, 2025
83 checks passed
@zmodem
Copy link
Collaborator

zmodem commented Dec 12, 2025

Thanks! This is finding stuff, including what looks like at least one bug: https://chromium-review.googlesource.com/c/chromium/src/+/7256034/2/third_party/blink/renderer/platform/fonts/font_metrics.cc

anonymouspc pushed a commit to anonymouspc/llvm that referenced this pull request Dec 15, 2025
`[[nodiscard]]` should be applied to functions where discarding the
return value is most likely a correctness issue.

- https://libcxx.llvm.org/CodingGuidelines.html
- https://wg21.link/optional

---------

Co-authored-by: William Tran-Viet <wtranviet@proton.me>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants