Possible breaking changes for v4
Each change on this page would improve toml++, but would also break something that users rely on today. They wait for the next major version, and none of them is decided.
Library
Static path::subpath() overload
toml::path::subpath(const_iterator, const_iterator) doesn't use the path it's called on, so it could be a static member function (#310). Calls through an object would still compile.
The exported symbol would change from a const member function to a static one, so programs linked against a shared build of toml++ would need rebuilding.
Paths and empty keys
A toml::path is meant to be relative to the node it's applied to, but TOML allows empty keys, and that makes some paths ambiguous. toml::path("") is one empty key. A default-constructed path is empty, and so is one built from a string that fails to parse. An empty path selects the node it's applied to, unless that node is a value or an empty container. str() loses the . between an empty key and an index, so a..[1] prints as a.[1], which is a different path.
Some other quirks: a . directly before an index is ignored, so a.[1] is the same path as a[1]. The string overloads of at_path() select nothing for a string that fails to parse, and the path overloads select the node itself for the empty path that string gives. Assigning such a string to a path empties it, and appending one leaves the path unchanged.
The path tests pin all of this as v3.4.0 had it, and v3 keeps it that way.
How paths work, and how they should work, needs a review from the ground up. Any change here would change what existing paths select or how they print.
Reverse iteration over tables
A toml::table iterator returns its key-value pair as a proxy object stored inside the iterator. std::reverse_iterator dereferences a temporary copy of the iterator, so the reference it returns outlives the proxy, and std::views::reverse does the same. Returning the proxy by value would fix it.
The iterators' reference type would change, so loops like for (auto& [k, v] : tbl) would stop compiling.
parse_result conversions without exceptions
With exceptions disabled, toml::parse_result converts implicitly to table&, table&& and const table&, and none of the conversions is ref-qualified. So toml::table t = result; is ambiguous, and toml::table& t = toml::parse(doc); compiles and dangles. Ref-qualifying the conversions fixes both, though direct-initialisation from an rvalue stays ambiguous.
Code that takes a table&& from an lvalue parse_result, or a table& from a temporary one, would stop compiling.
Comparing date-times as instants
toml::date_time compares field by field. 1979-05-27T07:32:00Z and 1979-05-27T00:32:00-07:00 are the same instant but compare unequal, and < orders by local date and time before the offset. Offset date-times could compare as instants instead, with some rule for comparing them to local ones.
Equality of tables and arrays holding date-times would change with it, and so would the order of sorted containers of them.
Node storage
Parsing allocates once for every node, every table entry, every non-empty array, and every key or string too long for the small-string buffer. A parsed 8 MB document holds about 455,000 allocations, the fewest the current types allow. These would need fewer:
- tables as a sorted vector of key-value pairs, with one allocation per table where
std::mapmakes one per entry - tables and arrays holding their values in place, where each value is a separately allocated node today
- keys and strings that refer into one copy of the document
- one source path per document, where every node's source region holds a
std::shared_ptrto it today, and each copy is an atomic increment
Each changes the layout of a public type. The first two would also make inserting into a table or array invalidate references to its other elements, which stay valid today, and the third would change what toml::key and toml::value<std::string> hold.
A smaller node interface
toml::node has 35 virtual functions: the destructor, type(), twelve is_*() checks, eighteen as_*() casts and three is_homogeneous() overloads. So there are ten vtables of 38 entries, for node and its nine concrete types, and each concrete type has its own copy of every override, most of them a single return statement. In a program that parses and prints, they come to as much as 20 KB.
Only type() and is_homogeneous() need to be virtual. The checks and casts could be inline functions that test type(). That changes every node vtable and removes exported virtual functions, so it breaks the shared library's ABI.
Constructors that allocate under noexcept
The default and move constructors of toml::table and toml::array are noexcept. With the MSVC STL, std::map allocates in both of its own, and in debug builds std::vector does too, so running out of memory while constructing or moving a table calls std::terminate() where it could throw std::bad_alloc.
Dropping the noexcept would let it throw. Code that tests these constructors with std::is_nothrow_* or noexcept() would see the change, and so would containers that move elements only when moving is noexcept.
Detecting _Float16
TOML_ENABLE_FLOAT16 defaults to 0, so _Float16 support is opt-in. Detecting it from the compiler's predefined __FLT16_* macros breaks GCC 7 to 12 on ARM, which define the macros in C++ without accepting the type there (#57, #178, #186). GCC 13 and newer, and Clang, define the macros only where they accept the type, so detection could be limited to them.
Turning it on by default would break code that sets TOML_SMALL_FLOAT_TYPE to _Float16, or to std::float16_t in C++23 with GCC, because the library would then define its traits for that type twice.
Unicode bare keys
With TOML_ENABLE_UNRELEASED_FEATURES, bare keys can hold the non-ASCII characters that toml-lang/
Documents that use such keys would stop parsing until the keys are quoted.
CMake
Minimum CMake version
The minimum is CMake 3.14. Raising it to 3.21 would let project() set PROJECT_IS_TOP_LEVEL, so cmake/project-is-top-level.cmake could go. Copying runtime DLLs next to the Windows test executables would also stop needing a version check.
It would break anyone building with CMake 3.14 to 3.20, which includes the 3.16 that Ubuntu 20.04 ships.
Option names
The options use three naming styles. Most have a lowercase prefix, like tomlplusplus_INSTALL, tomlplusplus_BUILD_LIB and tomlplusplus_INSTALL_CMAKEDIR. TOMLPLUSPLUS_BUILD_MODULES is all uppercase, and BUILD_EXAMPLES and BUILD_FUZZER have no prefix. CMake convention is all uppercase, so every option would become TOMLPLUSPLUS_*.
Each renamed option breaks the scripts and recipes that set it, and one of them lives outside this repository. The OSS-Fuzz builder image copies fuzzing/build.sh from master, and CIFuzz runs master's copy against every pull request. BUILD_FUZZER has to keep working until master's script stops using it.
Package files for header-only installs
A header-only install puts the CMake package files in lib/cmake/tomlplusplus. They are the same on every architecture, so share/cmake/tomlplusplus suits them better, and Meson already uses it for header-only installs.
find_package() searches both locations. Anything that hard-codes the current path, such as a tomlplusplus_DIR setting, would break.
CPack
The top-level build includes CPack with no configuration, which adds a package target nobody has set up. Removing it takes that target away from anyone who runs it.
Meson
Version check
The tomlplusplusConfigVersion.cmake that Meson installs accepts any newer version, so find_package(tomlplusplus 2) finds 3.4. The one CMake installs only accepts the same major version, which matches the ABI namespace.
Making Meson's match would refuse version requests that succeed today.
Installed examples
With -Dbuild_examples=true, Meson installs the example programs into bin/. They exist to show the API, and the CMake build doesn't install them. Stopping it would remove files from installs that include them today.
Library type as a subproject
meson.build sets default_library=shared in its default_options. That is Meson's default anyway, so it only matters when toml++ is a subproject. There it overrides the parent project's default_library, and with Meson older than 1.8 it also overrides -Ddefault_library on the command line. Without it, the parent chooses.
Parent projects that build the library with build_lib=true and ask for static libraries would then get a static toml++, where they get a shared one today.
Repository
Tipi.build
The .tipi/ directory and the Tipi.build instructions in the README and on the main page support a package manager the project doesn't test against. Removing them would break Tipi users.