March 20, 2019·5 min read·engineeringcppclanglibcxxtooling

Clang 8 makes C++ header compatibility an explicit choice

libc++ 8 changes the requirements for mixing header versions in one executable. Clang's accompanying tools improve navigation and help express C++17 contracts.

C++ library compatibility depends on the headers used to compile each translation unit as well as the library selected at link time. libc++ 8 makes one consequence explicit. Programs that combine translation units built against different versions of its headers must enable a particular compatibility macro. Leaving it disabled can reduce code size, but mixing header versions without it can cause One Definition Rule violations and ABI problems.

That requirement appears in the libc++ 8.0.0 release notes, part of the LLVM release announced on March 20. It is a concrete example of the maintenance cost I associate with C++. Templates can provide abstractions without runtime overhead, while distributing the work of understanding those abstractions across compilation and linking. A compiler upgrade needs a coherent account of which headers produced the objects being linked.

The compatibility setting belongs to compilation

The macro is _LIBCPP_HIDE_FROM_ABI_PER_TU, and the required value is 1. According to the release notes, it must be defined when building translation units that will be combined with units built using another libc++ header version. This requirement applies to the final linked image. It is therefore relevant even when separate components have independent build procedures.

For a build that deliberately combines such objects, the definition belongs in the compilation settings for those translation units. A compiler command using libc++ can express it as follows, with the usual project include paths and other options added by the build system.

clang++ -stdlib=libc++ -D_LIBCPP_HIDE_FROM_ABI_PER_TU=1 \
  -c component.cpp -o component.o

Defining a macro during a final link cannot change how an already compiled translation unit used the headers. That follows from the release notes' instruction to define it when building those units. The practical task is to propagate the setting through the builds that produce the inputs, including independently built components where mixed header versions are intentional.

The code-size benefit explains why the setting is a choice. libc++'s maintainers expect improvements for users who do not define the macro. A build that uses one header version throughout can follow that configuration. A build that needs to combine versions must meet the documented compatibility requirement. These are different build contracts, and a distribution policy needs to say which one applies.

There is a separate removal in the same release. std::dynarray and std::bad_array_length have gone. Both came from proposed C++14 work that never became part of the standard. The presence of a name in a standard library implementation therefore did not make that name a portable standard facility.

The removal of std::bad_array_length formally breaks the ABI because the shared library shipped its symbols. The notes nevertheless say that the maintainers do not expect actual breakage. They also explain the macOS case, where availability annotations prevented use of the feature. That distinction is useful when interpreting release notes. A formal compatibility change and a prediction about affected users answer different questions.

Clang 8 also improves the tools used to understand a large C++ program. The Extra Clang Tools release notes describe clangd's use of a global symbol index for definition and reference queries. When that index is available, it supplements the information accessible from the file being edited.

The same index supports completion for symbols that the current file has not imported. clangd can suggest a symbol and insert the missing include directive. Completion also adds namespace qualifiers, so typing a prefix such as vec can produce a suggestion for std::vector. The editor can help locate a declaration without requiring the programmer to recall its full qualified name first.

The qualification about index availability matters. The notes describe what clangd does with a global index, not a promise that every editor session already has a complete one. They also describe a new index designed for complex and fuzzy queries over large codebases. Index construction and access are part of the language server's implementation, separate from the compiler accepting a source file.

There is useful integration work around that distinction. A new language-server extension lets a client supply compilation commands directly instead of requiring discovery through compile_commands.json. Another communicates activity on clangd's worker thread for a file, where AST construction can block other operations. An editor can consequently expose that work to its user instead of leaving an unanswered request unexplained.

I find these changes relevant to the difficulty of reading C++ code written in several styles. Better reference queries reduce the work needed to establish which declaration a use belongs to. They do not simplify the language, but they make more of the program available to an engineer who is trying to understand one file.

A return-value contract needs judgement

The release adds clang-tidy's modernize-use-nodiscard check. Its versioned documentation explains which member functions qualify for a suggested C++17 attribute. Candidates have a non-void, non-template return type and are const members. The exclusions include pointer parameters, non-const reference parameters and classes with mutable members.

Those conditions try to identify functions whose useful result is their return value. An example of the resulting declaration is small enough to inspect directly. The attribute puts the return-value requirement next to the member's interface.

struct buffer {
    [[nodiscard]] bool empty() const;
};

The tool's documented heuristic still allows a function to perform external operations such as I/O. A const member function is not necessarily free of side effects. Deciding whether ignoring its return value is a mistake therefore requires knowledge of the interface. The check identifies candidates for a contract that the author must understand.

For projects that also support compilers predating C++17, the check has a ReplacementString option. It can insert a project macro in place of the attribute. The macro must already be defined in scope for clang-tidy to apply the fix. That requirement lets a project reuse its existing portability convention instead of receiving a spelling its older compiler cannot accept.

The Clang 8 release notes also describe a change to libc++ include-directory discovery on macOS. Discovery now follows the compiler's filesystem path instead of -resource-dir. Users who relied on overriding that flag may need explicit flags for the appropriate headers. This complements libc++'s mixed-header requirement. Both changes make the selection of headers consequential, so build configurations must record which headers each compilation actually uses.