Fix std::terminate when converting std::optional with MSVC 2017

MSVC 2017 evaluates std::is_nothrow_assignable<json&, const T&> as true
even if T's to_json throws, so to_json(json&, const std::optional<T>&)
was noexcept there and the exception from #5642's test called
std::terminate instead of propagating. Make that conversion never
noexcept on MSVC 2017; all other compilers keep the exact condition.
The static_asserts on the condition are skipped for MSVC 2017; the
runtime check that the exception propagates still runs there.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann committed 2026-10-05 20:08:17 +02:00
1 parent 8c05e40cae
commit 2234cd2d40
3 files changed
+27 -5

No files matched your search

@@ -287,9 +287,20 @@ struct external_constructor<value_t::object>
///////////// /////////////
#ifdef JSON_HAS_CPP_17 #ifdef JSON_HAS_CPP_17
// whether storing the value of a std::optional<T> cannot throw; MSVC 2017
// evaluates std::is_nothrow_assignable as true even if T's to_json throws, so
// the exception would call std::terminate (#5642)
#if defined(_MSC_VER) && !defined(__clang__) && _MSC_VER < 1920
template<typename BasicJsonType, typename T>
using is_nothrow_optional_to_json = std::false_type;
#else
template<typename BasicJsonType, typename T>
using is_nothrow_optional_to_json = std::is_nothrow_assignable<BasicJsonType&, const T&>;
#endif
template<typename BasicJsonType, typename T, template<typename BasicJsonType, typename T,
enable_if_t<std::is_constructible<BasicJsonType, T>::value, int> = 0> enable_if_t<std::is_constructible<BasicJsonType, T>::value, int> = 0>
void to_json(BasicJsonType& j, const std::optional<T>& opt) noexcept(std::is_nothrow_assignable<BasicJsonType&, const T&>::value) void to_json(BasicJsonType& j, const std::optional<T>& opt) noexcept(is_nothrow_optional_to_json<BasicJsonType, T>::value)
{ {
if (opt.has_value()) if (opt.has_value())
{ {
+12 -1
View File
@@ -7081,9 +7081,20 @@ struct external_constructor<value_t::object>
///////////// /////////////
#ifdef JSON_HAS_CPP_17 #ifdef JSON_HAS_CPP_17
// whether storing the value of a std::optional<T> cannot throw; MSVC 2017
// evaluates std::is_nothrow_assignable as true even if T's to_json throws, so
// the exception would call std::terminate (#5642)
#if defined(_MSC_VER) && !defined(__clang__) && _MSC_VER < 1920
template<typename BasicJsonType, typename T>
using is_nothrow_optional_to_json = std::false_type;
#else
template<typename BasicJsonType, typename T>
using is_nothrow_optional_to_json = std::is_nothrow_assignable<BasicJsonType&, const T&>;
#endif
template<typename BasicJsonType, typename T, template<typename BasicJsonType, typename T,
enable_if_t<std::is_constructible<BasicJsonType, T>::value, int> = 0> enable_if_t<std::is_constructible<BasicJsonType, T>::value, int> = 0>
void to_json(BasicJsonType& j, const std::optional<T>& opt) noexcept(std::is_nothrow_assignable<BasicJsonType&, const T&>::value) void to_json(BasicJsonType& j, const std::optional<T>& opt) noexcept(is_nothrow_optional_to_json<BasicJsonType, T>::value)
{ {
if (opt.has_value()) if (opt.has_value())
{ {
+3 -3
View File
@@ -853,11 +853,11 @@ TEST_CASE("std::optional")
CHECK_THROWS_WITH_AS(json(opt), "cannot serialize throwing_to_json_type", std::runtime_error&); CHECK_THROWS_WITH_AS(json(opt), "cannot serialize throwing_to_json_type", std::runtime_error&);
// the conversion is noexcept exactly when converting the contained value is // the conversion is noexcept exactly when converting the contained value is
// (MSVC 2017 evaluates the conditional noexcept of this conversion as true) // (except with MSVC 2017, where it is never noexcept, see to_json.hpp)
#if !defined(_MSC_VER) || _MSC_VER >= 1920 #if !defined(_MSC_VER) || defined(__clang__) || _MSC_VER >= 1920
static_assert(!std::is_nothrow_constructible<json, const std::optional<throwing_to_json_type>&>::value); static_assert(!std::is_nothrow_constructible<json, const std::optional<throwing_to_json_type>&>::value);
#endif
static_assert(std::is_nothrow_constructible<json, const std::optional<int>&>::value); static_assert(std::is_nothrow_constructible<json, const std::optional<int>&>::value);
#endif
} }
#endif #endif
} }