[libcxx-commits] [libcxx] [libcxx] Enrich message for std::bad_variant_access exception (PR #196495)

Nikita Grivin via libcxx-commits libcxx-commits at lists.llvm.org
Fri May 8 02:12:41 PDT 2026


https://github.com/MindSpectre created https://github.com/llvm/llvm-project/pull/196495

## Summary

`std::bad_variant_access::what()` now returns a more descriptive message
identifying the failing operation (e.g. `std::get: variant is valueless`)
instead of the generic `bad_variant_access`. Brings libc++ to parity with
libstdc++, which has provided richer messages for years.

Discussed in: [RFC](https://discourse.llvm.org/t/rfc-improve-bad-variant-access-what-messages/90716)

Adds an internal derived class of `bad_variant_access` carrying a `const char*`
message, exported from the dylib with availability annotations. The base
`bad_variant_access` class is unchanged — no ABI flag required, no layout
change to the existing type. Throw sites in `__generic_get` and
`__throw_if_valueless` instantiate the derived type with the appropriate
literal; user code catching `bad_variant_access const&` picks up the new
`what()` via virtual dispatch.

Distinguishes three failure modes:
- `std::get: wrong alternative for variant`
- `std::get: variant is valueless`
- `std::visit: variant is valueless`

## ABI
Updated `x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.abilist`
based on a local Linux build. Other platforms' ABI lists will need updates
per CI feedback - I don't have access to those toolchains for local verification.

>From fa3f002e4540781697359fd8e9623285bc929594 Mon Sep 17 00:00:00 2001
From: Polaris <neuronspectrelin at gmail.com>
Date: Fri, 8 May 2026 01:24:05 +0300
Subject: [PATCH] [libcxx] Enrich message for std::bad_variant_access exception

std::bad_variant_access::what() now returns a more descriptive message identifying the failing operation (e.g., std::get: variant is valueless) instead of the generic bad_variant_access
---
 libcxx/docs/ReleaseNotes/23.rst               |  4 +
 libcxx/include/__configuration/availability.h | 14 +++
 libcxx/include/variant                        | 27 +++++-
 ...bcxxabi.v1.stable.exceptions.nonew.abilist |  4 +
 libcxx/src/variant.cpp                        |  4 +
 .../good_what_message.pass.cpp                | 92 +++++++++++++++++++
 6 files changed, 141 insertions(+), 4 deletions(-)
 create mode 100644 libcxx/test/libcxx/utilities/variant/variant.bad_variant_access/good_what_message.pass.cpp

diff --git a/libcxx/docs/ReleaseNotes/23.rst b/libcxx/docs/ReleaseNotes/23.rst
index a34e379b145fe..4ec0f5febeac0 100644
--- a/libcxx/docs/ReleaseNotes/23.rst
+++ b/libcxx/docs/ReleaseNotes/23.rst
@@ -58,6 +58,10 @@ Improvements and New Features
   for ``std::deque<int>`` iterators.
 - ``std::copy(CharT*, CharT*, ostreambuf_iterator<CharT>)`` has been optimized, resulting in performance improvements
   of up to 25x.
+- ``std::bad_variant_access::what()`` now returns a message identifying the cause of the failure:
+  ``"std::visit: variant is valueless"``, ``"std::get: variant is valueless"``, or
+  ``"std::get: wrong alternative for variant"``. The standard only requires ``what()`` to return an
+  unspecified non-null string, so user code that does not match on the exact message remains correct.
 
 Deprecations and Removals
 -------------------------
diff --git a/libcxx/include/__configuration/availability.h b/libcxx/include/__configuration/availability.h
index f1a598b5206f4..7420004489a97 100644
--- a/libcxx/include/__configuration/availability.h
+++ b/libcxx/include/__configuration/availability.h
@@ -39,6 +39,9 @@
 // in all versions of the library are available.
 #if !_LIBCPP_HAS_VENDOR_AVAILABILITY_ANNOTATIONS
 
+#  define _LIBCPP_INTRODUCED_IN_LLVM_23 1
+#  define _LIBCPP_INTRODUCED_IN_LLVM_23_ATTRIBUTE /* nothing */
+
 #  define _LIBCPP_INTRODUCED_IN_LLVM_22 1
 #  define _LIBCPP_INTRODUCED_IN_LLVM_22_ATTRIBUTE /* nothing */
 
@@ -70,6 +73,11 @@
 
 // clang-format off
 
+// LLVM 23
+// TODO: Fill this in
+#  define _LIBCPP_INTRODUCED_IN_LLVM_23 0
+#  define _LIBCPP_INTRODUCED_IN_LLVM_23_ATTRIBUTE __attribute__((unavailable))
+
 // LLVM 22
 // TODO: Fill this in
 #  define _LIBCPP_INTRODUCED_IN_LLVM_22 0
@@ -230,6 +238,12 @@
 #define _LIBCPP_AVAILABILITY_HAS_BAD_FUNCTION_CALL_GOOD_WHAT_MESSAGE _LIBCPP_INTRODUCED_IN_LLVM_21
 // No attribute, since we've had bad_function_call::what() in the headers before
 
+// This controls whether we provide messages for `bad_variant_access::what()` that describe the
+// cause of the failure (visit-on-valueless vs. get-with-wrong-alternative). This requires
+// anchoring an internal derived class's vtable, typeinfo, and what() in the dylib.
+#define _LIBCPP_AVAILABILITY_HAS_BAD_VARIANT_ACCESS_GOOD_WHAT_MESSAGE _LIBCPP_INTRODUCED_IN_LLVM_23
+// No attribute, since we've had bad_variant_access in the headers before
+
 // This controls the availability of floating-point std::from_chars functions.
 // These overloads were added later than the integer overloads.
 #define _LIBCPP_AVAILABILITY_HAS_FROM_CHARS_FLOATING_POINT _LIBCPP_INTRODUCED_IN_LLVM_20
diff --git a/libcxx/include/variant b/libcxx/include/variant
index 9b2c4ee23ddcf..8341edb2fd628 100644
--- a/libcxx/include/variant
+++ b/libcxx/include/variant
@@ -291,6 +291,17 @@ public:
   [[__nodiscard__]] const char* what() const _NOEXCEPT override;
 };
 
+#  if _LIBCPP_AVAILABILITY_HAS_BAD_VARIANT_ACCESS_GOOD_WHAT_MESSAGE
+class _LIBCPP_EXPORTED_FROM_ABI __bad_variant_access_with_msg : public bad_variant_access {
+public:
+  _LIBCPP_HIDE_FROM_ABI explicit __bad_variant_access_with_msg(const char* __msg) noexcept : __msg_(__msg) {}
+  [[__nodiscard__]] const char* what() const _NOEXCEPT override;
+
+private:
+  const char* __msg_;
+};
+#  endif
+
 _LIBCPP_END_UNVERSIONED_NAMESPACE_STD
 
 _LIBCPP_BEGIN_NAMESPACE_STD
@@ -307,11 +318,16 @@ struct __farray {
   _LIBCPP_HIDE_FROM_ABI constexpr const _Tp& operator[](size_t __n) const noexcept { return __buf_[__n]; }
 };
 
-[[noreturn]] inline _LIBCPP_HIDE_FROM_ABI void __throw_bad_variant_access() {
+[[noreturn]] inline _LIBCPP_HIDE_FROM_ABI void __throw_bad_variant_access(const char* __msg) {
 #    if _LIBCPP_HAS_EXCEPTIONS
+#      if _LIBCPP_AVAILABILITY_HAS_BAD_VARIANT_ACCESS_GOOD_WHAT_MESSAGE
+  throw __bad_variant_access_with_msg(__msg);
+#      else
+  (void)__msg;
   throw bad_variant_access();
+#      endif
 #    else
-  _LIBCPP_VERBOSE_ABORT("bad_variant_access was thrown in -fno-exceptions mode");
+  _LIBCPP_VERBOSE_ABORT("bad_variant_access was thrown in -fno-exceptions mode: %s", __msg);
 #    endif
 }
 
@@ -1330,7 +1346,10 @@ template <size_t _Ip, class _Vp>
 _LIBCPP_HIDE_FROM_ABI constexpr auto&& __generic_get(_Vp&& __v) {
   using __variant_detail::__access::__variant;
   if (!std::__holds_alternative<_Ip>(__v)) {
-    std::__throw_bad_variant_access();
+    if (__v.valueless_by_exception()) {
+      std::__throw_bad_variant_access("std::get: variant is valueless");
+    }
+    std::__throw_bad_variant_access("std::get: wrong alternative for variant");
   }
   return __variant::__get_alt<_Ip>(std::forward<_Vp>(__v)).__value;
 }
@@ -1567,7 +1586,7 @@ template <class... _Vs>
 _LIBCPP_HIDE_FROM_ABI constexpr void __throw_if_valueless(_Vs&&... __vs) {
   const bool __valueless = (... || std::__as_variant(__vs).valueless_by_exception());
   if (__valueless) {
-    std::__throw_bad_variant_access();
+    std::__throw_bad_variant_access("std::visit: variant is valueless");
   }
 }
 
diff --git a/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.abilist b/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.abilist
index e5372de54c40e..5188294c84818 100644
--- a/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.abilist
+++ b/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.abilist
@@ -65,6 +65,7 @@
 {'is_defined': True, 'name': '_ZNKSt16nested_exception14rethrow_nestedEv', 'type': 'FUNC'}
 {'is_defined': True, 'name': '_ZNKSt18bad_variant_access4whatEv', 'type': 'FUNC'}
 {'is_defined': True, 'name': '_ZNKSt19bad_optional_access4whatEv', 'type': 'FUNC'}
+{'is_defined': True, 'name': '_ZNKSt29__bad_variant_access_with_msg4whatEv', 'type': 'FUNC'}
 {'is_defined': True, 'name': '_ZNKSt3__110__time_put8__do_putEPcRS1_PK2tmcc', 'type': 'FUNC'}
 {'is_defined': True, 'name': '_ZNKSt3__110__time_put8__do_putEPwRS1_PK2tmcc', 'type': 'FUNC'}
 {'is_defined': True, 'name': '_ZNKSt3__110error_code7messageEv', 'type': 'FUNC'}
@@ -1750,6 +1751,7 @@
 {'is_defined': True, 'name': '_ZTISt16nested_exception', 'size': 16, 'type': 'OBJECT'}
 {'is_defined': True, 'name': '_ZTISt18bad_variant_access', 'size': 24, 'type': 'OBJECT'}
 {'is_defined': True, 'name': '_ZTISt19bad_optional_access', 'size': 24, 'type': 'OBJECT'}
+{'is_defined': True, 'name': '_ZTISt29__bad_variant_access_with_msg', 'size': 24, 'type': 'OBJECT'}
 {'is_defined': True, 'name': '_ZTSNSt12experimental15fundamentals_v112bad_any_castE', 'size': 50, 'type': 'OBJECT'}
 {'is_defined': True, 'name': '_ZTSNSt12experimental19bad_optional_accessE', 'size': 40, 'type': 'OBJECT'}
 {'is_defined': True, 'name': '_ZTSNSt3__110__time_getE', 'size': 21, 'type': 'OBJECT'}
@@ -1883,6 +1885,7 @@
 {'is_defined': True, 'name': '_ZTSSt16nested_exception', 'size': 21, 'type': 'OBJECT'}
 {'is_defined': True, 'name': '_ZTSSt18bad_variant_access', 'size': 23, 'type': 'OBJECT'}
 {'is_defined': True, 'name': '_ZTSSt19bad_optional_access', 'size': 24, 'type': 'OBJECT'}
+{'is_defined': True, 'name': '_ZTSSt29__bad_variant_access_with_msg', 'size': 34, 'type': 'OBJECT'}
 {'is_defined': True, 'name': '_ZTTNSt3__110istrstreamE', 'size': 32, 'type': 'OBJECT'}
 {'is_defined': True, 'name': '_ZTTNSt3__110ostrstreamE', 'size': 32, 'type': 'OBJECT'}
 {'is_defined': True, 'name': '_ZTTNSt3__113basic_istreamIcNS_11char_traitsIcEEEE', 'size': 16, 'type': 'OBJECT'}
@@ -2008,6 +2011,7 @@
 {'is_defined': True, 'name': '_ZTVSt16nested_exception', 'size': 32, 'type': 'OBJECT'}
 {'is_defined': True, 'name': '_ZTVSt18bad_variant_access', 'size': 40, 'type': 'OBJECT'}
 {'is_defined': True, 'name': '_ZTVSt19bad_optional_access', 'size': 40, 'type': 'OBJECT'}
+{'is_defined': True, 'name': '_ZTVSt29__bad_variant_access_with_msg', 'size': 40, 'type': 'OBJECT'}
 {'is_defined': True, 'name': '_ZThn16_NSt3__114basic_iostreamIcNS_11char_traitsIcEEED0Ev', 'type': 'FUNC'}
 {'is_defined': True, 'name': '_ZThn16_NSt3__114basic_iostreamIcNS_11char_traitsIcEEED1Ev', 'type': 'FUNC'}
 {'is_defined': True, 'name': '_ZThn16_NSt3__19strstreamD0Ev', 'type': 'FUNC'}
diff --git a/libcxx/src/variant.cpp b/libcxx/src/variant.cpp
index b5462d64fe952..c213cb00feb88 100644
--- a/libcxx/src/variant.cpp
+++ b/libcxx/src/variant.cpp
@@ -12,4 +12,8 @@ namespace std {
 
 const char* bad_variant_access::what() const noexcept { return "bad_variant_access"; }
 
+#if _LIBCPP_AVAILABILITY_HAS_BAD_VARIANT_ACCESS_GOOD_WHAT_MESSAGE
+const char* __bad_variant_access_with_msg::what() const noexcept { return __msg_; }
+#endif
+
 } // namespace std
diff --git a/libcxx/test/libcxx/utilities/variant/variant.bad_variant_access/good_what_message.pass.cpp b/libcxx/test/libcxx/utilities/variant/variant.bad_variant_access/good_what_message.pass.cpp
new file mode 100644
index 0000000000000..707fddca84c27
--- /dev/null
+++ b/libcxx/test/libcxx/utilities/variant/variant.bad_variant_access/good_what_message.pass.cpp
@@ -0,0 +1,92 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, no-exceptions
+
+// Verify that std::bad_variant_access::what() returns a message describing the
+// cause of the failure for std::get and std::visit.
+
+#include <cassert>
+#include <cstring>
+#include <variant>
+
+struct ThrowOnMove {
+  ThrowOnMove() = default;
+  ThrowOnMove(ThrowOnMove&&) { throw 0; }
+};
+
+int main(int, char**) {
+#if _LIBCPP_AVAILABILITY_HAS_BAD_VARIANT_ACCESS_GOOD_WHAT_MESSAGE
+  // std::get<I> on the wrong alternative
+  {
+    std::variant<int, long> v(42);
+    try {
+      (void)std::get<1>(v);
+      assert(false);
+    } catch (const std::bad_variant_access& e) {
+      assert(std::strcmp(e.what(), "std::get: wrong alternative for variant") == 0);
+    }
+  }
+  // std::get<T> on the wrong alternative
+  {
+    std::variant<int, long> v(42);
+    try {
+      (void)std::get<long>(v);
+      assert(false);
+    } catch (const std::bad_variant_access& e) {
+      assert(std::strcmp(e.what(), "std::get: wrong alternative for variant") == 0);
+    }
+  }
+  // std::get<I> on a valueless variant
+  {
+    std::variant<int, ThrowOnMove> v(42);
+    try {
+      v.emplace<1>(ThrowOnMove{});
+    } catch (...) {
+    }
+    assert(v.valueless_by_exception());
+    try {
+      (void)std::get<0>(v);
+      assert(false);
+    } catch (const std::bad_variant_access& e) {
+      assert(std::strcmp(e.what(), "std::get: variant is valueless") == 0);
+    }
+  }
+  // std::get<T> on a valueless variant
+  {
+    std::variant<int, ThrowOnMove> v(42);
+    try {
+      v.emplace<1>(ThrowOnMove{});
+    } catch (...) {
+    }
+    assert(v.valueless_by_exception());
+    try {
+      (void)std::get<int>(v);
+      assert(false);
+    } catch (const std::bad_variant_access& e) {
+      assert(std::strcmp(e.what(), "std::get: variant is valueless") == 0);
+    }
+  }
+  // std::visit on a valueless variant
+  {
+    std::variant<int, ThrowOnMove> v(42);
+    try {
+      v.emplace<1>(ThrowOnMove{});
+    } catch (...) {
+    }
+    assert(v.valueless_by_exception());
+    try {
+      std::visit([](auto&&) {}, v);
+      assert(false);
+    } catch (const std::bad_variant_access& e) {
+      assert(std::strcmp(e.what(), "std::visit: variant is valueless") == 0);
+    }
+  }
+#endif
+  return 0;
+}



More information about the libcxx-commits mailing list