diff --git a/include/nlohmann/detail/conversions/to_json.hpp b/include/nlohmann/detail/conversions/to_json.hpp index ee6164c5a..5f8644700 100644 --- a/include/nlohmann/detail/conversions/to_json.hpp +++ b/include/nlohmann/detail/conversions/to_json.hpp @@ -177,8 +177,11 @@ struct external_constructor } template < typename BasicJsonType, typename CompatibleArrayType, - enable_if_t < !std::is_same::value, - int > = 0 > + enable_if_t < !std::is_same::value +#if JSON_HAS_RANGES && !defined(__MINGW32__) + && !is_compatible_range_view::value +#endif + , int > = 0 > static void construct(BasicJsonType& j, const CompatibleArrayType& arr) { using std::begin; @@ -218,6 +221,25 @@ struct external_constructor j.set_parents(); j.assert_invariant(); } + + // std::ranges does not work properly on MinGW due to incomplete C++20 support + // see https://github.com/nlohmann/json/issues/4916 +#if JSON_HAS_RANGES && !defined(__MINGW32__) + template>::value, int> = 0> + static void construct(BasicJsonType& j, CompatibleArrayType && arr) + { + j.m_data.m_value.destroy(j.m_data.m_type); + j.m_data.m_type = value_t::array; + j.m_data.m_value = value_t::array; + for (auto&& x : std::forward(arr)) + { + j.m_data.m_value.array->push_back(x); + j.set_parent(j.m_data.m_value.array->back()); + } + j.assert_invariant(); + } +#endif }; template<> @@ -356,13 +378,29 @@ template < typename BasicJsonType, typename CompatibleArrayType, !is_compatible_string_type::value&& !std::is_same::value&& !is_compatible_binary_type::value&& - !is_basic_json::value, + !is_basic_json::value +#if JSON_HAS_RANGES && !defined(__MINGW32__) + && !is_compatible_range_view::value +#endif + , int > = 0 > inline void to_json(BasicJsonType& j, const CompatibleArrayType& arr) { external_constructor::construct(j, arr); } +#if JSON_HAS_RANGES && !defined(__MINGW32__) +template < typename BasicJsonType, typename T, + enable_if_t < is_compatible_range_view>::value + && !is_compatible_string_type>::value + && !is_compatible_object_type>::value + && !is_basic_json>::value, int > = 0 > +inline void to_json(BasicJsonType& j, T && arr) +{ + external_constructor::construct(j, std::forward(arr)); +} +#endif + template inline void to_json(BasicJsonType& j, const typename BasicJsonType::binary_t& bin) { diff --git a/include/nlohmann/detail/meta/type_traits.hpp b/include/nlohmann/detail/meta/type_traits.hpp index 15aa88832..7036a4ef0 100644 --- a/include/nlohmann/detail/meta/type_traits.hpp +++ b/include/nlohmann/detail/meta/type_traits.hpp @@ -19,6 +19,9 @@ #endif #include #include +#ifdef JSON_HAS_CPP_17 + #include // optional +#endif #include #include #include @@ -451,6 +454,51 @@ struct is_constructible_string_type value_type_t, laundered_type >>::value; }; +// Forward declarations: iteration_proxy.hpp includes this file, so we cannot +// include it here. +template class iteration_proxy; +template class iteration_proxy_value; + +// Identifies nlohmann's internal iteration-proxy types. These must be excluded +// before evaluating any std::ranges concept to avoid circular constraints. +template struct is_iteration_proxy_type : std::false_type {}; +template struct is_iteration_proxy_type> : std::true_type {}; +template struct is_iteration_proxy_type> : std::true_type {}; + +// In C++26, std::optional satisfies std::ranges::view; exclude it so the +// range-view overload does not hijack the optional serializer. +#ifdef JSON_HAS_CPP_17 +template struct is_range_view_optional_type : std::false_type {}; +template struct is_range_view_optional_type> : std::true_type {}; +#else +template struct is_range_view_optional_type : std::false_type {}; +#endif + +// std::ranges does not work properly on MinGW due to incomplete C++20 support +// see https://github.com/nlohmann/json/issues/4916 +#if JSON_HAS_RANGES && !defined(__MINGW32__) + +// SafeToCheck guards against types that trigger circular constraints when +// std::ranges::view is evaluated on GCC 12 / libstdc++ 12: +// - iteration_proxy / iteration_proxy_value directly +// - views wrapping the above (e.g. owning_view>) +// - views wrapping basic_json (e.g. ref_view) — same circularity +// via json's constructors → is_compatible_array_type → here +// nlohmann's plain range_value_t (iterator_traits-based) is safe to call +// before any std::ranges concept is touched, so we use it for the checks. +template < typename T, bool SafeToCheck = + !is_iteration_proxy_type::value && + !is_iteration_proxy_type>::value && + !is_basic_json>::value && + !is_range_view_optional_type::value > +struct is_compatible_range_view : std::false_type {}; + +template +struct is_compatible_range_view + : std::bool_constant> {}; + +#endif + template struct is_compatible_array_type_impl : std::false_type {}; @@ -462,13 +510,38 @@ struct is_compatible_array_type_impl < is_iterator_traits>>::value&& // special case for types like std::filesystem::path whose iterator's value_type are themselves // c.f. https://github.com/nlohmann/json/pull/3073 - !std::is_same>::value >> + !std::is_same>::value +// When range-view support is enabled, std::ranges::view types (e.g. std::string_view, +// filter_view) can match BOTH this iterator-based specialization AND the view-based one +// below, causing ambiguity. Exclude views here so the two specializations are mutually +// exclusive: this one handles plain iterable containers, the other handles views. +#if JSON_HAS_RANGES && !defined(__MINGW32__) +&& !is_compatible_range_view::value +#endif + >> { static constexpr bool value = is_constructible>::value; }; +#if JSON_HAS_RANGES && !defined(__MINGW32__) +template +struct is_compatible_array_type_impl < + BasicJsonType, CompatibleArrayType, + enable_if_t < is_compatible_range_view::value + && !std::is_same, char>::value + && !std::is_same, wchar_t>::value >> +{ + // CompatibleArrayType is a std::ranges::view here, so std::ranges::range_value_t + // is safe and correctly handles C++20 iterators that may lack classic iterator_traits. + static constexpr bool value = + is_constructible>::value; +}; +#endif + + template struct is_compatible_array_type : is_compatible_array_type_impl {}; diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index e2afdda11..6952210c1 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -3665,6 +3665,9 @@ NLOHMANN_JSON_NAMESPACE_END // #include +#ifdef JSON_HAS_CPP_17 + #include // optional +#endif // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ @@ -4214,6 +4217,51 @@ struct is_constructible_string_type value_type_t, laundered_type >>::value; }; +// Forward declarations: iteration_proxy.hpp includes this file, so we cannot +// include it here. +template class iteration_proxy; +template class iteration_proxy_value; + +// Identifies nlohmann's internal iteration-proxy types. These must be excluded +// before evaluating any std::ranges concept to avoid circular constraints. +template struct is_iteration_proxy_type : std::false_type {}; +template struct is_iteration_proxy_type> : std::true_type {}; +template struct is_iteration_proxy_type> : std::true_type {}; + +// In C++26, std::optional satisfies std::ranges::view; exclude it so the +// range-view overload does not hijack the optional serializer. +#ifdef JSON_HAS_CPP_17 +template struct is_range_view_optional_type : std::false_type {}; +template struct is_range_view_optional_type> : std::true_type {}; +#else +template struct is_range_view_optional_type : std::false_type {}; +#endif + +// std::ranges does not work properly on MinGW due to incomplete C++20 support +// see https://github.com/nlohmann/json/issues/4916 +#if JSON_HAS_RANGES && !defined(__MINGW32__) + +// SafeToCheck guards against types that trigger circular constraints when +// std::ranges::view is evaluated on GCC 12 / libstdc++ 12: +// - iteration_proxy / iteration_proxy_value directly +// - views wrapping the above (e.g. owning_view>) +// - views wrapping basic_json (e.g. ref_view) — same circularity +// via json's constructors → is_compatible_array_type → here +// nlohmann's plain range_value_t (iterator_traits-based) is safe to call +// before any std::ranges concept is touched, so we use it for the checks. +template < typename T, bool SafeToCheck = + !is_iteration_proxy_type::value && + !is_iteration_proxy_type>::value && + !is_basic_json>::value && + !is_range_view_optional_type::value > +struct is_compatible_range_view : std::false_type {}; + +template +struct is_compatible_range_view + : std::bool_constant> {}; + +#endif + template struct is_compatible_array_type_impl : std::false_type {}; @@ -4225,13 +4273,38 @@ struct is_compatible_array_type_impl < is_iterator_traits>>::value&& // special case for types like std::filesystem::path whose iterator's value_type are themselves // c.f. https://github.com/nlohmann/json/pull/3073 - !std::is_same>::value >> + !std::is_same>::value +// When range-view support is enabled, std::ranges::view types (e.g. std::string_view, +// filter_view) can match BOTH this iterator-based specialization AND the view-based one +// below, causing ambiguity. Exclude views here so the two specializations are mutually +// exclusive: this one handles plain iterable containers, the other handles views. +#if JSON_HAS_RANGES && !defined(__MINGW32__) +&& !is_compatible_range_view::value +#endif + >> { static constexpr bool value = is_constructible>::value; }; +#if JSON_HAS_RANGES && !defined(__MINGW32__) +template +struct is_compatible_array_type_impl < + BasicJsonType, CompatibleArrayType, + enable_if_t < is_compatible_range_view::value + && !std::is_same, char>::value + && !std::is_same, wchar_t>::value >> +{ + // CompatibleArrayType is a std::ranges::view here, so std::ranges::range_value_t + // is safe and correctly handles C++20 iterators that may lack classic iterator_traits. + static constexpr bool value = + is_constructible>::value; +}; +#endif + + template struct is_compatible_array_type : is_compatible_array_type_impl {}; @@ -6235,8 +6308,11 @@ struct external_constructor } template < typename BasicJsonType, typename CompatibleArrayType, - enable_if_t < !std::is_same::value, - int > = 0 > + enable_if_t < !std::is_same::value +#if JSON_HAS_RANGES && !defined(__MINGW32__) + && !is_compatible_range_view::value +#endif + , int > = 0 > static void construct(BasicJsonType& j, const CompatibleArrayType& arr) { using std::begin; @@ -6276,6 +6352,25 @@ struct external_constructor j.set_parents(); j.assert_invariant(); } + + // std::ranges does not work properly on MinGW due to incomplete C++20 support + // see https://github.com/nlohmann/json/issues/4916 +#if JSON_HAS_RANGES && !defined(__MINGW32__) + template>::value, int> = 0> + static void construct(BasicJsonType& j, CompatibleArrayType && arr) + { + j.m_data.m_value.destroy(j.m_data.m_type); + j.m_data.m_type = value_t::array; + j.m_data.m_value = value_t::array; + for (auto&& x : std::forward(arr)) + { + j.m_data.m_value.array->push_back(x); + j.set_parent(j.m_data.m_value.array->back()); + } + j.assert_invariant(); + } +#endif }; template<> @@ -6414,13 +6509,29 @@ template < typename BasicJsonType, typename CompatibleArrayType, !is_compatible_string_type::value&& !std::is_same::value&& !is_compatible_binary_type::value&& - !is_basic_json::value, + !is_basic_json::value +#if JSON_HAS_RANGES && !defined(__MINGW32__) + && !is_compatible_range_view::value +#endif + , int > = 0 > inline void to_json(BasicJsonType& j, const CompatibleArrayType& arr) { external_constructor::construct(j, arr); } +#if JSON_HAS_RANGES && !defined(__MINGW32__) +template < typename BasicJsonType, typename T, + enable_if_t < is_compatible_range_view>::value + && !is_compatible_string_type>::value + && !is_compatible_object_type>::value + && !is_basic_json>::value, int > = 0 > +inline void to_json(BasicJsonType& j, T && arr) +{ + external_constructor::construct(j, std::forward(arr)); +} +#endif + template inline void to_json(BasicJsonType& j, const typename BasicJsonType::binary_t& bin) { diff --git a/tests/src/unit-regression2.cpp b/tests/src/unit-regression2.cpp index 91dc10dbf..7ae4170d3 100644 --- a/tests/src/unit-regression2.cpp +++ b/tests/src/unit-regression2.cpp @@ -1199,6 +1199,46 @@ TEST_CASE("regression tests 2") } #endif +#if JSON_HAS_RANGES && !defined(__MINGW32__) + SECTION("issue #4916 - constructing array from C++20 ranges view does not work") + { + std::vector nums{1, 2, 37, 42, 21}; + auto filteredNums = nums | std::views::filter([](int i) + { + return i > 10; + }); + json const j(filteredNums); + CHECK(j.type() == json::value_t::array); + CHECK(j == json({37, 42, 21})); + } +#endif + + // owning_view is not available in libstdc++ < 12 +#if JSON_HAS_RANGES && !defined(__MINGW32__) && !(defined(__GLIBCXX__) && _GLIBCXX_RELEASE < 12) + SECTION("issue #4916 - constructing array from prvalue C++20 ranges view (owning_view)") + { + json const j(std::vector {1, 2, 37, 42, 21} | std::views::filter([](int i) + { + return i > 10; + })); + CHECK(j.type() == json::value_t::array); + CHECK(j == json({37, 42, 21})); + } +#endif + +#if JSON_HAS_RANGES && !defined(__MINGW32__) + SECTION("issue #4916 - constructing array from C++20 transform view (prvalue elements)") + { + std::vector nums{1, 2, 3}; + auto t = nums | std::views::transform([](int i) noexcept + { + return i * 2; + }); + json const j(t); + CHECK(j.type() == json::value_t::array); + CHECK(j == json({2, 4, 6})); + } +#endif } TEST_CASE_TEMPLATE("issue #4798 - nlohmann::json::to_msgpack() encode float NaN as double", T, double, float) // NOLINT(readability-math-missing-parentheses, bugprone-throwing-static-initialization)