Krystian's April Update
May 8, 2020Overview
Boost 1.73.0 has been released! Save for some minor documentation issues, Boost.StaticString enjoyed a bug-free release, so most of this month was spent working on Boost.JSON getting it ready for review. Unfortunately, I could not spend too much time working due to school and final exams, but now that those have passed I’ll be able to put in significantly more time working on projects such as Boost.JSON.
Boost.JSON
A good portion of my work on Boost.JSON was spent updating the documentation to reflect the replacement of the storage
allocator model with boost::container::pmr::memory_resource
(or std::pmr::memory_resource
in standalone). The old model wasn’t necessarily bad, but using memory_resource
permits the use of existing allocators found in Boost.Container/the standard library, eliminating the need for writing proprietary allocators that only work with Boost.JSON.
Even though storage
will be going away, storage_ptr
will remain to support shared ownership of a memory_resource
– something that polymorphic_allocator
lacks. As with polymorphic_allocator
, storage_ptr
will still support non-owning reference semantics in contexts where the lifetime of a memory_resource
is bound to a scope, giving users more flexibility.
I also worked on monotonic_resource
, the memory_resource
counterpart to pool
. This allocator has one goal: to be fast. I ended up adding the following features to facilitate this (mostly from monotonic_buffer_resource
):
- Construction from an initial buffer,
- The ability to reset the allocator without releasing memory, and
- The ability to set a limit on the number of bytes that can be dynamically allocated.
The implementations of these features are pretty trivial, but they provide significant opportunities to cut down on dynamic allocations. For example, when parsing a large number of JSON documents, a single monotonic_resource
can be used and reset in between the parsing of each document without releasing any dynamically allocated storage. While care should be taken to destroy objects that occupy the storage before the allocator is reset, this can substantially reduce the number of allocations required and thus result in non-trivial performance gains.
The other major thing I worked on was fixing an overload resolution bug on clang-cl involving json::value
. This was originally brought to my attention by Vinnie when the CI build for clang-cl started reporting that overload resolution for value({false, 1, "2"})
was ambiguous. After a few hours of investigating, I found that false
was being treated as a null pointer constant – something that was certainly annoying, but it also didn’t fully explain why this error was happening.
After this unfortunate discovery, I tried again with value({0, 1, "2"})
, this time on clang, and it turns out this was a problem here as well. After many hours of testing, I found that the constructor in storage_ptr
taking a parameter of type memory_resource
had a small problem: its constraint was missing ::type
after the enable_if
, allowing storage_ptr
to be constructed from any pointer type, including const char*
. This somewhat helped to alleviate the problem, but value({false, false, false})
was still failing. After many more hours of groking the standard and trying to reproduce the error, I finally came upon the following json::string
constructors:
string(string const& other, std::size_t pos, std::size_t count = npos, storage_ptr sp = {})
string(string_view other, std::size_t pos, std::size_t count = npos, storage_ptr sp = {})
See the problem here? Since the first parameter of both constructors can be constructed from null pointer constants, overload resolution for string(0, 0, 0)
would be ambiguous. However, this isn’t the full story. Consider the following constructors for value
:
value(std::initializer_list<value_ref> init)
value(string str)
For the initialization of value({0, 0, 0})
the implicit conversion sequence to str
would be ambiguous, but the one to value_ref
can be formed. There is a special rule for overload resolution (separate from two-stage overload resolution during list-initialization) that considers any list-initialization sequence that converts to std::initializer_list
to be a better conversion sequence than one that does not, with the exception to this rule being that it only applies when the two conversion sequences are otherwise identical.
This rule should apply here, however, I found that clang has a small bug that prevents this rule from going into effect if any of the candidates have an ambiguous conversion sequence for the same parameter. We solve this pretty trivially by removing some of the redundant constructor overloads in json::string
and all was well. It was a fun little puzzle to solve (the explanation was a bit of an oversimplification; if you have questions please let me know).
If you want to get in touch with me, you can message me on the Cpplang slack, or shoot me an email.
All Posts by This Author
- 01/12/2024 Krystian's Q4 Update
- 10/31/2023 Krystian's Q3 Update
- 09/29/2020 Krystian's September Update
- 09/06/2020 Krystian's August Update
- 08/01/2020 Krystian's July Update
- 07/01/2020 Krystian's May & June Update
- 05/08/2020 Krystian's April Update
- 04/07/2020 Krystian's March Update
- 03/06/2020 Krystian's February Update
- View All Posts...