docs: qualify the std::pmr::string support claim

Listing std::pmr::string as fully supported was an overclaim: it was only ever
checked with the default memory resource, which is not what PMR is for.

basic_json cannot be given an allocator or a memory resource, so a pmr string
inside a value always allocates from std::pmr::get_default_resource(), and
assigning an arena-backed string into a value silently drops its resource,
because polymorphic_allocator does not propagate on copy construction. Passing
polymorphic_allocator as AllocatorType does not compile either. Only the
process-global set_default_resource() redirects these allocations.

Say so, and separate the row from std::basic_string with a custom stateless
allocator, which is unaffected.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann
2026-08-28 17:38:57 +00:00
parent 681fb07eb2
commit ca47dd539d
@@ -289,6 +289,23 @@ using array_t = ArrayType<basic_json, AllocatorType<basic_json>>;
| [`std::hash<basic_json>`](../../api/basic_json/std_hash.md) | a specialization of `#!cpp std::hash<StringType>` | | [`std::hash<basic_json>`](../../api/basic_json/std_hash.md) | a specialization of `#!cpp std::hash<StringType>` |
| exception messages | `data()` and `size()`, or `begin()` and `end()` | | exception messages | `data()` and `size()`, or `begin()` and `end()` |
!!! warning "A `std::pmr::string` does not allocate from a memory resource you choose"
`basic_json` cannot be given an allocator or a memory resource. `AllocatorType` is default-constructed at every
allocation and has to be stateless (see [`AllocatorType`](#allocatortype)), and string values are constructed with
their own default allocator. Three consequences:
- A `#!cpp std::pmr::string` inside a `basic_json` always allocates from
`#!cpp std::pmr::get_default_resource()`.
- Assigning an arena-backed string into a value **silently drops its memory resource**: the copy lands on the
default resource, because `#!cpp std::pmr::polymorphic_allocator` does not propagate on copy construction.
Nothing warns about this.
- Passing `#!cpp std::pmr::polymorphic_allocator` as `AllocatorType` does not work around it; it does not compile.
The only way to redirect these allocations is the process-global `#!cpp std::pmr::set_default_resource()`. So
`#!cpp std::pmr::string` gives you the indirection cost of PMR without per-instance arena allocation. Use it if
you need that string type for interface reasons, not to control where the data lives.
!!! tip "Reference implementation" !!! tip "Reference implementation"
The unit test `tests/src/unit-alt-string.cpp` contains `alt_string`, a minimal string type that satisfies the The unit test `tests/src/unit-alt-string.cpp` contains `alt_string`, a minimal string type that satisfies the
@@ -299,7 +316,8 @@ using array_t = ArrayType<basic_json, AllocatorType<basic_json>>;
| Type | Support | | Type | Support |
|-----------------------------------------------------------------------------|-----------------------------------------------------------------------------| |-----------------------------------------------------------------------------|-----------------------------------------------------------------------------|
| `#!cpp std::string` (default) | full | | `#!cpp std::string` (default) | full |
| `#!cpp std::pmr::string`, `#!cpp std::basic_string` with a custom allocator | full | | `#!cpp std::basic_string` with a custom **stateless** allocator | full |
| `#!cpp std::pmr::string` | compiles and behaves correctly, but always allocates from the default memory resource -- see below |
| `boost::container::string` | full, once a `#!cpp std::hash` specialization is supplied (Boost provides `boost::hash` instead) | | `boost::container::string` | full, once a `#!cpp std::hash` specialization is supplied (Boost provides `boost::hash` instead) |
| `folly::fbstring` | full; requires C++20, see the note below | | `folly::fbstring` | full; requires C++20, see the note below |
| `eastl::string` | full, except that [`parse`](../../api/basic_json/parse.md) does not accept it directly; pass a character range or a `#!cpp std::string` | | `eastl::string` | full, except that [`parse`](../../api/basic_json/parse.md) does not accept it directly; pass a character range or a `#!cpp std::string` |
@@ -440,7 +458,8 @@ binary32 or binary64 field and have no encoding for `#!cpp long double`.
- It must be **default-constructible and stateless**. Objects are allocated with a default-constructed allocator and - It must be **default-constructible and stateless**. Objects are allocated with a default-constructed allocator and
deallocated with a *different* default-constructed allocator, and deallocated with a *different* default-constructed allocator, and
[`get_allocator()`](../../api/basic_json/get_allocator.md) returns a default-constructed instance. Allocators [`get_allocator()`](../../api/basic_json/get_allocator.md) returns a default-constructed instance. Allocators
carrying state are not supported. carrying state are not supported, so there is no way to tell a `basic_json` where to allocate from; see the note
under [`StringType`](#stringtype) for what that means in practice.
- It must support **incomplete types**: `AllocatorType<basic_json>` is instantiated inside the definition of - It must support **incomplete types**: `AllocatorType<basic_json>` is instantiated inside the definition of
`basic_json` itself. `basic_json` itself.
- `#!cpp std::allocator_traits<AllocatorType<basic_json>>::pointer` becomes - `#!cpp std::allocator_traits<AllocatorType<basic_json>>::pointer` becomes