diff --git a/include/nlohmann/detail/conversions/from_json.hpp b/include/nlohmann/detail/conversions/from_json.hpp index 0332474b2..a2c82923c 100644 --- a/include/nlohmann/detail/conversions/from_json.hpp +++ b/include/nlohmann/detail/conversions/from_json.hpp @@ -266,6 +266,22 @@ template void from_json_array_reserve(ConstructibleArrayType& /*arr*/, std::size_t /*size*/, priority_tag<0> /*unused*/) {} +// append through push_back() where available; containers without it (such as +// std::set) fall back to insert(end(), ...), which is what std::inserter did +template +auto from_json_array_append(ConstructibleArrayType& arr, ValueType&& value, priority_tag<1> /*unused*/) +-> decltype(arr.push_back(std::forward(value)), void()) +{ + arr.push_back(std::forward(value)); +} + +template +void from_json_array_append(ConstructibleArrayType& arr, ValueType&& value, priority_tag<0> /*unused*/) +{ + using std::end; + arr.insert(end(arr), std::forward(value)); +} + template::value, @@ -275,17 +291,15 @@ auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, p j.template get(), void()) { - using std::end; - ConstructibleArrayType ret; - from_json_array_reserve(ret, j.size(), priority_tag<1> {}); - std::transform(j.begin(), j.end(), - std::inserter(ret, end(ret)), [](const BasicJsonType & i) + const auto* inner_array = j.template get_ptr(); + from_json_array_reserve(ret, inner_array->size(), priority_tag<1> {}); + for (const auto& elem : *inner_array) { // get() returns *this, this won't call a from_json // method when value_type is BasicJsonType - return i.template get(); - }); + from_json_array_append(ret, elem.template get(), priority_tag<1> {}); + } arr = std::move(ret); } diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 3bad024f1..a5efac4ba 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -3333,6 +3333,22 @@ public: return j.m_data.m_value.object->find(lookup_key(std::forward(key))); } + /// @brief out-of-line throw helpers: building the message inline blocks inlining of the hot accessors + /// @throw out_of_range.401 + JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE + static void throw_array_index_out_of_range(size_type idx, const basic_json& j) + { + JSON_THROW(out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat("array index ", std::to_string(idx), " is out of range"), &j)); + } + + /// @throw out_of_range.403 + template + JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE + static void throw_key_not_found(const KeyType& key, const basic_json& j) + { + JSON_THROW(out_of_range::create(detail::exception_id::key_not_found, detail::concat("key '", key, "' not found"), &j)); + } + /// @brief checked object element access used by the at() overloads taking a key /// @throw type_error.304 if @a j is not an object /// @throw out_of_range.403 if @a key is not found @@ -3353,7 +3369,7 @@ public: // std::map or ordered_map) never moves from its argument, so key is still // valid here regardless of whether KeyType was deduced as an rvalue reference // NOLINTNEXTLINE(bugprone-use-after-move,hicpp-invalid-access-moved) - JSON_THROW(out_of_range::create(detail::exception_id::key_not_found, detail::concat("key '", key_for_message(key), "' not found"), &j)); + throw_key_not_found(key_for_message(key), j); } return it->second; } @@ -3390,7 +3406,7 @@ public: if (JSON_HEDLEY_UNLIKELY(idx >= j.m_data.m_value.array->size())) { - JSON_THROW(out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat("array index ", std::to_string(idx), " is out of range"), &j)); + throw_array_index_out_of_range(idx, j); } return (*j.m_data.m_value.array)[idx]; diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index a35f9e32c..9abd2bd0b 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -5962,6 +5962,22 @@ template void from_json_array_reserve(ConstructibleArrayType& /*arr*/, std::size_t /*size*/, priority_tag<0> /*unused*/) {} +// append through push_back() where available; containers without it (such as +// std::set) fall back to insert(end(), ...), which is what std::inserter did +template +auto from_json_array_append(ConstructibleArrayType& arr, ValueType&& value, priority_tag<1> /*unused*/) +-> decltype(arr.push_back(std::forward(value)), void()) +{ + arr.push_back(std::forward(value)); +} + +template +void from_json_array_append(ConstructibleArrayType& arr, ValueType&& value, priority_tag<0> /*unused*/) +{ + using std::end; + arr.insert(end(arr), std::forward(value)); +} + template::value, @@ -5971,17 +5987,15 @@ auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, p j.template get(), void()) { - using std::end; - ConstructibleArrayType ret; - from_json_array_reserve(ret, j.size(), priority_tag<1> {}); - std::transform(j.begin(), j.end(), - std::inserter(ret, end(ret)), [](const BasicJsonType & i) + const auto* inner_array = j.template get_ptr(); + from_json_array_reserve(ret, inner_array->size(), priority_tag<1> {}); + for (const auto& elem : *inner_array) { // get() returns *this, this won't call a from_json // method when value_type is BasicJsonType - return i.template get(); - }); + from_json_array_append(ret, elem.template get(), priority_tag<1> {}); + } arr = std::move(ret); } @@ -32681,6 +32695,22 @@ public: return j.m_data.m_value.object->find(lookup_key(std::forward(key))); } + /// @brief out-of-line throw helpers: building the message inline blocks inlining of the hot accessors + /// @throw out_of_range.401 + JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE + static void throw_array_index_out_of_range(size_type idx, const basic_json& j) + { + JSON_THROW(out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat("array index ", std::to_string(idx), " is out of range"), &j)); + } + + /// @throw out_of_range.403 + template + JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE + static void throw_key_not_found(const KeyType& key, const basic_json& j) + { + JSON_THROW(out_of_range::create(detail::exception_id::key_not_found, detail::concat("key '", key, "' not found"), &j)); + } + /// @brief checked object element access used by the at() overloads taking a key /// @throw type_error.304 if @a j is not an object /// @throw out_of_range.403 if @a key is not found @@ -32701,7 +32731,7 @@ public: // std::map or ordered_map) never moves from its argument, so key is still // valid here regardless of whether KeyType was deduced as an rvalue reference // NOLINTNEXTLINE(bugprone-use-after-move,hicpp-invalid-access-moved) - JSON_THROW(out_of_range::create(detail::exception_id::key_not_found, detail::concat("key '", key_for_message(key), "' not found"), &j)); + throw_key_not_found(key_for_message(key), j); } return it->second; } @@ -32738,7 +32768,7 @@ public: if (JSON_HEDLEY_UNLIKELY(idx >= j.m_data.m_value.array->size())) { - JSON_THROW(out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat("array index ", std::to_string(idx), " is out of range"), &j)); + throw_array_index_out_of_range(idx, j); } return (*j.m_data.m_value.array)[idx]; diff --git a/tests/benchmarks/src/benchmarks.cpp b/tests/benchmarks/src/benchmarks.cpp index 5df7dd473..c6d2c6ddb 100644 --- a/tests/benchmarks/src/benchmarks.cpp +++ b/tests/benchmarks/src/benchmarks.cpp @@ -9,7 +9,10 @@ #include #include #include +#include +#include #include +#include #include #include @@ -621,4 +624,51 @@ static void FromCborChunkedString(benchmark::State& state, const std::size_t chu BENCHMARK_CAPTURE(FromCborChunkedString, 10000 chunks, 10000); +// checked element access (at(i) must inline like operator[]) +static void ArrayAt(benchmark::State& state) +{ + json j = json::array(); + for (int i = 0; i < 1000; ++i) + { + j.push_back(i); + } + const json& cj = j; + + for (auto _ : state) + { + std::int64_t sum = 0; + for (std::size_t i = 0; i < 1000; ++i) + { + sum += cj.at(i).get(); + } + benchmark::DoNotOptimize(sum); + } + state.SetItemsProcessed(state.iterations() * 1000); +} + +BENCHMARK(ArrayAt); + +// conversion of a JSON array into standard containers +template +static void GetArray(benchmark::State& state) +{ + json j = json::array(); + for (int i = 0; i < 1000; ++i) + { + j.push_back(i); + } + + for (auto _ : state) + { + auto c = j.get(); + benchmark::DoNotOptimize(c); + } + state.SetItemsProcessed(state.iterations() * 1000); +} + +BENCHMARK_TEMPLATE(GetArray, std::vector); +BENCHMARK_TEMPLATE(GetArray, std::deque); +BENCHMARK_TEMPLATE(GetArray, std::set); +BENCHMARK_TEMPLATE(GetArray, std::array); + BENCHMARK_MAIN();