Header Removal Policy¶
Policy¶
Libc++ is in the process of splitting larger headers into smaller modular
headers. This makes it possible to remove these large headers from other
headers. For example, instead of including <algorithm>
entirely it is
possible to only include the headers for the algorithms used. When the
Standard indirectly adds additional header includes, using the smaller headers
aids reducing the growth of top-level headers. For example <atomic>
uses
std::chrono::nanoseconds
and included <chrono>
. In C++20 <chrono>
requires <format>
which adds several other headers (like <string>
,
<optional>
, <tuple>
) which are not needed in <atomic>
.
The benefit of using minimal headers is that the size of libc++’s top-level headers becomes smaller. This improves the compilation time when users include a top-level header. It also avoids header inclusion cycles and makes it easier to port headers to platforms with reduced functionality.
A disadvantage is that users unknowingly depend on these transitive includes.
Thus removing an include might break their build after upgrading a newer
version of libc++ by reducing the set of declarations provided by a header.
For example, <algorithm>
is often forgotten but using algorithms will
still work through those transitive includes. This problem is solved by modules,
however in practice most people do not use modules (yet).
To ease the removal of transitive includes in libc++, libc++ will remove unnecessary transitive includes in newly supported C++ versions. This means that users will have to fix their missing includes in order to upgrade to a newer version of the Standard. Libc++ also reserves the right to remove transitive includes at any other time, however new language versions will be used as a convenient way to perform bulk removals of transitive includes.
However, libc++ intends not to gratuitously break users on stable versions of the Standard. Hence, we intend to maintain backwards compatibility of the declarations we provide in a header, within reason. We reserve the right to break such backwards compatibility in the future, however we will strive to do it in a user-friendly way, again within reason. For libc++ developers, this means that any transitive include removal of a public header must be guarded by something of the form:
#if !defined(_LIBCPP_REMOVE_TRANSITIVE_INCLUDES) && _LIBCPP_STD_VER <= 23
# include <algorithm>
# include <iterator>
# include <utility>
#endif
Occasionally, private headers may also be included transitively for backwards compatibility in the same manner. We currently strive to provide backwards compatibility on the set of declarations provided by a header in all Standard modes starting with C++23. Note that this is very difficult to actually enforce, so this is done only on a best effort basis.
When users define _LIBCPP_REMOVE_TRANSITIVE_INCLUDES
, libc++ will not include
transitive headers, regardless of the language version. This can be useful for users
to aid the transition to a newer language version, or by users who simply want to
make sure they include what they use in their code. However, note that defining this
macro means that the set of declarations and transitive includes provided by the library
may change from release to release, which can break your code.
Rationale¶
Removing headers is not only an issue for software developers, but also for vendors. When a vendor updates libc++ several of their upstream packages might fail to compile, forcing them to fix these packages or file a bug with their upstream packages. Usually upgrading software to a new language standard is done explicitly by software developers. This means they most likely will discover and fix the missing includes, lessening the burden for the vendors.