From beb83ae03b0b680f5c9f7ac52b6130b275151133 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Fri, 9 Oct 2026 16:12:05 +0200 Subject: [PATCH] Copy const rvalue strings and document the accepted inputs json_document::parse(std::move(const_string)) failed to compile with "no matching read_kind": only a non-const rvalue std::string can be moved from. Treat a const rvalue as a copied byte container. parse() does not accept everything BasicJsonType::parse() does: a FILE* and pointers to or arrays of wide characters are rejected at compile time. List the supported inputs instead. Signed-off-by: Niels Lohmann --- .../docs/api/basic_json_document/parse.md | 29 ++++++++++++------- include/nlohmann/detail/view/input.hpp | 10 +++---- tests/src/unit-json_view.cpp | 13 +++++++++ 3 files changed, 37 insertions(+), 15 deletions(-) diff --git a/docs/mkdocs/docs/api/basic_json_document/parse.md b/docs/mkdocs/docs/api/basic_json_document/parse.md index 7c46e17ea..219634370 100644 --- a/docs/mkdocs/docs/api/basic_json_document/parse.md +++ b/docs/mkdocs/docs/api/basic_json_document/parse.md @@ -19,24 +19,33 @@ static basic_json_document parse(IteratorType first, IteratorType last, 1. Deserialize from a compatible input, borrowing or owning it depending on its value category and type (see Notes). 2. Deserialize from a pair of input iterators. -Both overloads accept exactly what [`BasicJsonType::parse()`](../basic_json/parse.md) accepts, with the same +Both overloads accept the same JSON text as [`BasicJsonType::parse()`](../basic_json/parse.md), with the same `ignore_comments`/`ignore_trailing_commas` options, but build a [`basic_json_document`](index.md) (a flat index into -the input) instead of a tree of `BasicJsonType` values. +the input) instead of a tree of `BasicJsonType` values. The input must be byte-oriented (see the template parameters +below): not every input type of `BasicJsonType::parse()` is supported. ## Template parameters `InputType` -: A compatible input, for instance: +: A byte-oriented input, one of: - - a `#!cpp std::string`, `#!cpp std::string_view`, or a C-style array of characters - - a pointer to a null-terminated string of single byte characters + - a `#!cpp std::string`, `#!cpp std::string_view`, or a C-style array of single-byte characters + - a pointer to a null-terminated string of single-byte characters (`#!cpp char`, `#!cpp signed char`, + `#!cpp unsigned char`, `#!cpp std::uint8_t`) - a container for which `#!cpp obj.data()` and `#!cpp obj.size()` give contiguous single-byte access, e.g. `#!cpp std::vector` or `#!cpp std::vector` - - an `#!cpp std::istream` object, or anything else [`BasicJsonType::parse()`](../basic_json/parse.md) accepts + - an `#!cpp std::istream` object + - a wide string object (`#!cpp std::wstring`, `#!cpp std::u16string`, `#!cpp std::u32string`), which is converted + to UTF-8 + + Other inputs are not supported: a `#!cpp FILE*`, and pointers to or arrays of wide characters (`#!cpp wchar_t`, + `#!cpp char16_t`, `#!cpp char32_t`) are rejected at compile time by a `#!cpp static_assert`. (Use + [`BasicJsonType::parse()`](../basic_json/parse.md) for these.) `IteratorType` -: a compatible iterator type, for instance a pair of pointers such as `ptr` and `ptr + len`, or a pair of - `#!cpp std::string::iterator` +: an input iterator type, for instance a pair of pointers such as `ptr` and `ptr + len`, or a pair of + `#!cpp std::string::iterator`; the iterators of single-byte characters are borrowed or read like the byte inputs + above, those of wide characters are converted to UTF-8 ## Parameters @@ -84,8 +93,8 @@ Linear in the length of the input. | `input` | ownership | |--------------------------------------------------------------------------------------|--------------------------------------------------------------| | lvalue byte container (`std::string`, `std::vector`, ...), `std::string_view`, C string, character array | **borrowed** -- `input` must outlive the document | -| rvalue `#!cpp std::string` | **owned**, moved in without a copy | -| rvalue byte container other than `#!cpp std::string` | **owned**, copied | +| non-const rvalue `#!cpp std::string` | **owned**, moved in without a copy | +| other rvalue byte container (including a `#!cpp const` rvalue `#!cpp std::string`) | **owned**, copied | | stream, wide string, or anything else read through the general input adapter | **owned**, read into a buffer (a stream is read to its end) | For overload (2), a pair of pointers to single-byte integers (e.g. `#!cpp const char*`, `#!cpp std::uint8_t*`) is diff --git a/include/nlohmann/detail/view/input.hpp b/include/nlohmann/detail/view/input.hpp index 3fafa2ecf..ac3257ee3 100644 --- a/include/nlohmann/detail/view/input.hpp +++ b/include/nlohmann/detail/view/input.hpp @@ -9,7 +9,7 @@ #pragma once #include // basic_string, char_traits, string -#include // decay, integral_constant, is_array, is_lvalue_reference, is_pointer, is_same, remove_reference +#include // decay, integral_constant, is_array, is_const, is_integral, is_lvalue_reference, is_pointer, is_same, remove_reference #include // forward #include @@ -28,12 +28,12 @@ namespace view /// how a document takes its input enum class input_kind { - move_string, ///< rvalue std::string: owned without a copy + move_string, ///< non-const rvalue std::string: owned without a copy c_string, ///< const char* (NUL-terminated): borrowed char_array, ///< char array (e.g. a string literal): borrowed borrow_range, ///< lvalue contiguous byte container, or std::string_view: borrowed - copy_range, ///< rvalue contiguous byte container: copied - adapter, ///< anything else parse() accepts (streams, wide strings, ...): read into a buffer + copy_range, ///< rvalue contiguous byte container (a const rvalue std::string too): copied + adapter, ///< streams, wide strings, and the rest of what the library's input adapter reads: read into a buffer }; template @@ -52,7 +52,7 @@ struct classify_input static constexpr input_kind value = std::is_array::value ? input_kind::char_array : std::is_pointer::value ? input_kind::c_string - : (is_rvalue && std::is_same::value) ? input_kind::move_string + : (is_rvalue && !std::is_const::value && std::is_same::value) ? input_kind::move_string : (is_bytes && (!is_rvalue || is_string_view)) ? input_kind::borrow_range : is_bytes ? input_kind::copy_range : input_kind::adapter; diff --git a/tests/src/unit-json_view.cpp b/tests/src/unit-json_view.cpp index 9df459d3a..3b38e078f 100644 --- a/tests/src/unit-json_view.cpp +++ b/tests/src/unit-json_view.cpp @@ -314,6 +314,19 @@ TEST_CASE("json_view") CHECK(from_rvalue.owns_source()); CHECK(from_rvalue.root().materialize() == expected); CHECK(json_document::parse(std::vector(text.begin(), text.end())).owns_source()); + // a const rvalue cannot be moved from, and is not borrowed (it may be a + // temporary): it is copied, as is a const rvalue of any container + const std::string const_text = text; + const json_document from_const_rvalue = json_document::parse(std::move(const_text)); // NOLINT(performance-move-const-arg,hicpp-move-const-arg) + CHECK(from_const_rvalue.owns_source()); + CHECK(from_const_rvalue.source().data() != const_text.data()); + CHECK(from_const_rvalue.root().materialize() == expected); + json_document read_const_rvalue; + read_const_rvalue.read(std::move(const_text)); // NOLINT(performance-move-const-arg,hicpp-move-const-arg) + CHECK(read_const_rvalue.owns_source()); + CHECK(read_const_rvalue.root().materialize() == expected); + const std::vector const_chars(text.begin(), text.end()); + CHECK(json_document::parse(std::move(const_chars)).owns_source()); // NOLINT(performance-move-const-arg,hicpp-move-const-arg) CHECK(json_document::parse_copy(text).owns_source()); CHECK(json_document::parse_copy(text).root().materialize() == expected); std::istringstream stream(text);