from_json: number-first dispatch for arithmetic types, loop for std::array (#5891)
from_json(ArithmeticType&) now reads numbers through get_number_value() and handles a boolean on a cold path, which puts the three number types first in the per-element type dispatch. get<std::array<T, N>>() always took the identity_tag overload, a pack expansion of N calls j.at(i).get<T>() that clang unrolls into straight-line code (about 28,000 instructions for N = 1000), so its speed depended on inlining decisions in that body. For trivial element types, which cannot tell default construction plus assignment from construction in place, it now fills a std::array in a loop. That loop, shared with get_to(), checks the size once and reads the inner array directly; a short array still converts the elements it has and then throws the out_of_range error at() would throw. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
3 files changed
+140
-88
No files matched your search
@@ -8,7 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm> // transform
|
||||
#include <algorithm> // min, transform
|
||||
#include <array> // array
|
||||
#include <forward_list> // forward_list
|
||||
#include <iterator> // inserter, front_inserter, end
|
||||
@@ -256,15 +256,30 @@ inline void from_json_array_impl(const BasicJsonType& j, typename BasicJsonType:
|
||||
arr = *j.template get_ptr<const typename BasicJsonType::array_t*>();
|
||||
}
|
||||
|
||||
// fill a std::array from the JSON array j: the size is checked once and the
|
||||
// elements are read in order, so a short array converts the elements it has and
|
||||
// then throws the out_of_range error at() would throw for the first missing one
|
||||
template<typename BasicJsonType, typename T, std::size_t N>
|
||||
void from_json_std_array(const BasicJsonType& j, std::array<T, N>& arr)
|
||||
{
|
||||
const auto& inner_array = *j.template get_ptr<const typename BasicJsonType::array_t*>();
|
||||
const std::size_t size = (std::min)(N, inner_array.size());
|
||||
for (std::size_t i = 0; i < size; ++i)
|
||||
{
|
||||
arr[i] = inner_array[i].template get<T>();
|
||||
}
|
||||
if (JSON_HEDLEY_UNLIKELY(size < N))
|
||||
{
|
||||
static_cast<void>(j.at(size));
|
||||
}
|
||||
}
|
||||
|
||||
template<typename BasicJsonType, typename T, std::size_t N>
|
||||
auto from_json_array_impl(const BasicJsonType& j, std::array<T, N>& arr,
|
||||
priority_tag<2> /*unused*/)
|
||||
-> decltype(j.template get<T>(), void())
|
||||
{
|
||||
for (std::size_t i = 0; i < N; ++i)
|
||||
{
|
||||
arr[i] = j.at(i).template get<T>();
|
||||
}
|
||||
from_json_std_array(j, arr);
|
||||
}
|
||||
|
||||
// reserve() is called through this pair (used for arrays and objects alike)
|
||||
@@ -347,6 +362,35 @@ std::array<T, sizeof...(Idx)> from_json_inplace_array_impl(const BasicJsonType&
|
||||
return { { j.at(Idx).template get<T>()... } };
|
||||
}
|
||||
|
||||
// std::is_trivial is deprecated in C++26 (see binary_writer::to_char_type)
|
||||
#ifdef JSON_HAS_CPP_26
|
||||
template<typename T>
|
||||
using is_trivial_element = conjunction<std::is_trivially_copyable<T>, std::is_trivially_default_constructible<T>>;
|
||||
#else
|
||||
template<typename T>
|
||||
using is_trivial_element = std::is_trivial<T>;
|
||||
#endif
|
||||
|
||||
// the element type may not be default constructible: construct every element
|
||||
// in place (a pack expansion that is unrolled N times)
|
||||
template < typename BasicJsonType, typename T, std::size_t N >
|
||||
std::array<T, N> from_json_inplace_array(const BasicJsonType& j, identity_tag<std::array<T, N>> tag,
|
||||
std::false_type /*is_trivial_element*/)
|
||||
{
|
||||
return from_json_inplace_array_impl(j, tag, make_index_sequence<N> {});
|
||||
}
|
||||
|
||||
// a trivial element type cannot tell default construction plus assignment from
|
||||
// construction in place, so use a loop instead of unrolling N conversions
|
||||
template < typename BasicJsonType, typename T, std::size_t N >
|
||||
std::array<T, N> from_json_inplace_array(const BasicJsonType& j, identity_tag<std::array<T, N>> /*unused*/,
|
||||
std::true_type /*is_trivial_element*/)
|
||||
{
|
||||
std::array<T, N> arr; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): every element is assigned or an exception is thrown
|
||||
from_json_std_array(j, arr);
|
||||
return arr;
|
||||
}
|
||||
|
||||
template < typename BasicJsonType, typename T, std::size_t N >
|
||||
auto from_json(const BasicJsonType& j, identity_tag<std::array<T, N>> tag)
|
||||
-> decltype(from_json_inplace_array_impl(j, tag, make_index_sequence<N> {}))
|
||||
@@ -356,7 +400,7 @@ auto from_json(const BasicJsonType& j, identity_tag<std::array<T, N>> tag)
|
||||
throw_type_must_be("array", j);
|
||||
}
|
||||
|
||||
return from_json_inplace_array_impl(j, tag, make_index_sequence<N> {});
|
||||
return from_json_inplace_array(j, tag, is_trivial_element<T> {});
|
||||
}
|
||||
|
||||
template<typename BasicJsonType>
|
||||
@@ -437,46 +481,17 @@ template < typename BasicJsonType, typename ArithmeticType,
|
||||
int > = 0 >
|
||||
inline void from_json(const BasicJsonType& j, ArithmeticType& val)
|
||||
{
|
||||
// the number cases repeat get_number_value() on purpose: calling it here
|
||||
// changes the inlining of get<T>() in the std::array conversion, which then
|
||||
// runs 2.4x slower (GetArray benchmark, #5891); unlike get_arithmetic_value(),
|
||||
// a boolean is accepted
|
||||
switch (static_cast<value_t>(j))
|
||||
if (get_number_value(j, val))
|
||||
{
|
||||
case value_t::number_unsigned:
|
||||
{
|
||||
// never null: a basic_json whose number_unsigned_t is number_integer_t
|
||||
// stores no number_unsigned values (#2573)
|
||||
const auto* ptr = j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>();
|
||||
JSON_ASSERT(ptr != nullptr);
|
||||
val = static_cast<ArithmeticType>(*ptr);
|
||||
break;
|
||||
}
|
||||
case value_t::number_integer:
|
||||
{
|
||||
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_integer_t*>());
|
||||
break;
|
||||
}
|
||||
case value_t::number_float:
|
||||
{
|
||||
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_float_t*>());
|
||||
break;
|
||||
}
|
||||
case value_t::boolean:
|
||||
{
|
||||
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::boolean_t*>());
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::null:
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
case value_t::string:
|
||||
case value_t::binary:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
throw_type_must_be("number", j);
|
||||
return;
|
||||
}
|
||||
|
||||
// unlike get_arithmetic_value(), a boolean is accepted here
|
||||
if (JSON_HEDLEY_UNLIKELY(!j.is_boolean()))
|
||||
{
|
||||
throw_type_must_be("number", j);
|
||||
}
|
||||
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::boolean_t*>());
|
||||
}
|
||||
|
||||
template<typename BasicJsonType, typename Type>
|
||||
|
||||
Reference in new issue
Block a user