Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/mkdocs/docs/api/basic_json/accept.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ Unlike the [`parse()`](parse.md) function, this function neither throws an excep
: defaults to `IteratorType`; may be a different type comparable to `IteratorType` via `operator!=`, for instance.

- a custom sentinel type for C++20 ranges
- `std::counted_iterator` with a different sentinel type
- `std::default_sentinel_t`, when `IteratorType` is `std::counted_iterator`

## Parameters

Expand Down
6 changes: 4 additions & 2 deletions docs/mkdocs/docs/api/basic_json/from_bjdata.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,10 @@ The exact mapping and its limitations are described on a [dedicated page](../../
: a compatible iterator type

`SentinelType`
: defaults to `IteratorType`; may be a different type comparable to `IteratorType` via `operator!=`, for instance a
custom sentinel type for C++20 ranges
: defaults to `IteratorType`; may be a different type comparable to `IteratorType` via `operator!=`, for instance.

- a custom sentinel type for C++20 ranges
- `std::default_sentinel_t`, when `IteratorType` is `std::counted_iterator`

## Parameters

Expand Down
6 changes: 4 additions & 2 deletions docs/mkdocs/docs/api/basic_json/from_bson.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,10 @@ The exact mapping and its limitations are described on a [dedicated page](../../
: a compatible iterator type

`SentinelType`
: defaults to `IteratorType`; may be a different type comparable to `IteratorType` via `operator!=`, for instance a
custom sentinel type for C++20 ranges
: defaults to `IteratorType`; may be a different type comparable to `IteratorType` via `operator!=`, for instance.

- a custom sentinel type for C++20 ranges
- `std::default_sentinel_t`, when `IteratorType` is `std::counted_iterator`

## Parameters

Expand Down
6 changes: 4 additions & 2 deletions docs/mkdocs/docs/api/basic_json/from_cbor.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,10 @@ The exact mapping and its limitations are described on a [dedicated page](../../
: a compatible iterator type

`SentinelType`
: defaults to `IteratorType`; may be a different type comparable to `IteratorType` via `operator!=`, for instance a
custom sentinel type for C++20 ranges
: defaults to `IteratorType`; may be a different type comparable to `IteratorType` via `operator!=`, for instance.

- a custom sentinel type for C++20 ranges
- `std::default_sentinel_t`, when `IteratorType` is `std::counted_iterator`

## Parameters

Expand Down
6 changes: 4 additions & 2 deletions docs/mkdocs/docs/api/basic_json/from_msgpack.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,10 @@ The exact mapping and its limitations are described on a [dedicated page](../../
: a compatible iterator type

`SentinelType`
: defaults to `IteratorType`; may be a different type comparable to `IteratorType` via `operator!=`, for instance a
custom sentinel type for C++20 ranges
: defaults to `IteratorType`; may be a different type comparable to `IteratorType` via `operator!=`, for instance.

- a custom sentinel type for C++20 ranges
- `std::default_sentinel_t`, when `IteratorType` is `std::counted_iterator`

## Parameters

Expand Down
6 changes: 4 additions & 2 deletions docs/mkdocs/docs/api/basic_json/from_ubjson.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,10 @@ The exact mapping and its limitations are described on a [dedicated page](../../
: a compatible iterator type

`SentinelType`
: defaults to `IteratorType`; may be a different type comparable to `IteratorType` via `operator!=`, for instance a
custom sentinel type for C++20 ranges
: defaults to `IteratorType`; may be a different type comparable to `IteratorType` via `operator!=`, for instance.

- a custom sentinel type for C++20 ranges
- `std::default_sentinel_t`, when `IteratorType` is `std::counted_iterator`

## Parameters

Expand Down
2 changes: 1 addition & 1 deletion docs/mkdocs/docs/api/basic_json/parse.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ static basic_json parse(IteratorType first, SentinelType last,
: defaults to `IteratorType`; may be a different type comparable to `IteratorType` via `operator!=`, for instance.

- a custom sentinel type for C++20 ranges
- `std::counted_iterator` with a different sentinel type
- `std::default_sentinel_t`, when `IteratorType` is `std::counted_iterator`

## Parameters

Expand Down
5 changes: 4 additions & 1 deletion docs/mkdocs/docs/api/basic_json/sax_parse.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ The SAX event lister must follow the interface of [`json_sax`](../json_sax/index
with a size of 1, 2, or 4 bytes (interpreted respectively as UTF-8, UTF-16, and UTF-32)

`SentinelType`
: defaults to `IteratorType`; may be a different type comparable to `IteratorType` via `operator!=`, for overload (2)
: defaults to `IteratorType`; may be a different type comparable to `IteratorType` via `operator!=`, for overload (2), for instance.

- a custom sentinel type for C++20 ranges
- `std::default_sentinel_t`, when `IteratorType` is `std::counted_iterator`

`SAX`
: a class fulfilling the SAX event listener interface; see [`json_sax`](../json_sax/index.md)
Expand Down
17 changes: 13 additions & 4 deletions include/nlohmann/detail/input/input_adapters.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -220,20 +220,29 @@ class iterator_input_adapter
// whether IteratorType refers to a contiguous range and therefore supports
// a std::memcpy fast path (pointers always do; in C++20 we can also detect
// library iterators such as those of std::vector and std::string).
// The fast path also requires SentinelType == IteratorType so std::distance works.
// Computing the available element count needs either same-type iterators
// (plain std::distance) or, in C++20, a sized sentinel (std::ranges::distance),
// e.g. std::counted_iterator paired with std::default_sentinel_t.
static constexpr bool iterator_is_contiguous =
std::is_same<IteratorType, SentinelType>::value && (
#if defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20)
std::contiguous_iterator<IteratorType> ||
(std::is_same<IteratorType, SentinelType>::value || std::sized_sentinel_for<SentinelType, IteratorType>)
&& (std::contiguous_iterator<IteratorType> || std::is_pointer<IteratorType>::value);
#else
std::is_same<IteratorType, SentinelType>::value && std::is_pointer<IteratorType>::value;
#endif
std::is_pointer<IteratorType>::value);

// contiguous fast path: bulk copy the remaining range with std::memcpy
template<class T>
std::size_t get_elements_impl(T* dest, std::size_t count, std::true_type /*contiguous*/)
{
const std::size_t wanted = count * sizeof(T);
#if defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20)
// std::ranges::distance also supports sized sentinels of a different
// type (e.g. std::counted_iterator + std::default_sentinel_t)
const std::size_t available = static_cast<std::size_t>(std::ranges::distance(current, end)) * sizeof(char_type);
#else
const std::size_t available = static_cast<std::size_t>(std::distance(current, end)) * sizeof(char_type);
#endif
const std::size_t copied = (std::min)(wanted, available);
if (JSON_HEDLEY_LIKELY(copied != 0))
{
Expand Down
17 changes: 13 additions & 4 deletions single_include/nlohmann/json.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7207,20 +7207,29 @@ class iterator_input_adapter
// whether IteratorType refers to a contiguous range and therefore supports
// a std::memcpy fast path (pointers always do; in C++20 we can also detect
// library iterators such as those of std::vector and std::string).
// The fast path also requires SentinelType == IteratorType so std::distance works.
// Computing the available element count needs either same-type iterators
// (plain std::distance) or, in C++20, a sized sentinel (std::ranges::distance),
// e.g. std::counted_iterator paired with std::default_sentinel_t.
static constexpr bool iterator_is_contiguous =
std::is_same<IteratorType, SentinelType>::value && (
#if defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20)
std::contiguous_iterator<IteratorType> ||
(std::is_same<IteratorType, SentinelType>::value || std::sized_sentinel_for<SentinelType, IteratorType>)
&& (std::contiguous_iterator<IteratorType> || std::is_pointer<IteratorType>::value);
#else
std::is_same<IteratorType, SentinelType>::value && std::is_pointer<IteratorType>::value;
#endif
std::is_pointer<IteratorType>::value);

// contiguous fast path: bulk copy the remaining range with std::memcpy
template<class T>
std::size_t get_elements_impl(T* dest, std::size_t count, std::true_type /*contiguous*/)
{
const std::size_t wanted = count * sizeof(T);
#if defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20)
// std::ranges::distance also supports sized sentinels of a different
// type (e.g. std::counted_iterator + std::default_sentinel_t)
const std::size_t available = static_cast<std::size_t>(std::ranges::distance(current, end)) * sizeof(char_type);
#else
const std::size_t available = static_cast<std::size_t>(std::distance(current, end)) * sizeof(char_type);
#endif
const std::size_t copied = (std::min)(wanted, available);
if (JSON_HEDLEY_LIKELY(copied != 0))
{
Expand Down
29 changes: 29 additions & 0 deletions tests/src/unit-user_defined_input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,24 @@
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT

// cmake/test.cmake selects the C++ standard versions with which to build a
// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros.
// When using macros that are only defined for particular versions of the standard
// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding
// version macro in a comment close by, like this:
// JSON_HAS_CPP_<VERSION> (do not remove; see note at top of file)

#include "doctest_compatibility.h"

#include <nlohmann/json.hpp>
using nlohmann::json;

#include <list>

#if defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20)
#include <iterator>
#endif

namespace
{
TEST_CASE("Use arbitrary stdlib container")
Expand Down Expand Up @@ -201,4 +212,22 @@ TEST_CASE("Parse with heterogeneous iterator and sentinel types")
CHECK(j2.at(0) == 1);
}

#if defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20)
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
TEST_CASE("Parse with std::counted_iterator and std::default_sentinel_t")
{
using iterator_type = std::string::const_iterator;
const std::string json_str = R"({"key":"value","array":[1,2,3]})";
const auto len = static_cast<std::iter_difference_t<iterator_type>>(json_str.size());

const std::counted_iterator<iterator_type> first(json_str.begin(), len);
const json j = json::parse(first, std::default_sentinel);
CHECK(j["key"] == "value");
CHECK(j["array"].size() == 3);

const std::counted_iterator<iterator_type> first2(json_str.begin(), len);
CHECK(json::accept(first2, std::default_sentinel));
}
#endif

} // namespace
Loading