[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