Documentation
The main docs for libstdc++ hacking are in Appendix A and Appendix B of the manual:
The Libstdcxx/WritingTests wiki page is useful too.
Header naming
stl_xxx.h is for headers that originated with the SGI STL. We do not use "STL" to refer to "the standard library", only to the subset that comes from the STL (as Wikipedia says, "The STL and the C++ Standard Library are two distinct entities.")
Name qualification
Except for the handful of functions that are explicitly intended as ADL customization points (e.g. swap, make_error_code, etc.) all calls to non-member functions should be qualified with a namespace, to prevent ADL.
It is necessary to disable ADL even for reserved names such as __rotate because otherwise ADL can cause compilation to fail when an associated class is incomplete (e.g., see Bug 60497, P2538R1, and P2600R0). Even without unwanted template instantiations, name lookup is typically faster to compile without ADL, because the compiler does not have to determine the associated namespaces and search in them.
C++20 Customization Point Objects
C++20 introduces a number of CPOs (std::ranges::swap, std::ranges::begin, std::strong_order etc.). These must be declared with care. Each CPO has an associated class type, and a "poison pill" overload that causes name lookup for the customization point to only consider names found by ADL. The poison pill works by ensuring that ordinary unqualified lookup for the name (e.g. begin) finds the poison pill, which is deleted and/or has the wrong number of arguments. Consequently, when the poison pill is visible, the only viable candidates for an unqualified call to that name will be overloads that are found via ADL. The CPO class type and the poison pill must be in a non-inline namespace nested below std, so that the poison pill is not visible for any name lookup except that done by the CPO type. The CPO object itself must be in a different namespace (so that it doesn't clash with the poison pill declaration). To avoid clashing with hidden friend functions with the same name, the CPO object must be in an inline namespace of its own.
So the definition of a CPO std::foo should look like:
1 namespace std {
2 namespace __foo {
3 void foo() = delete; // poison pill
4
5 // Test whether foo(t) can be found via ADL.
6 template<class T> concept __adl_foo = requires(T t) { foo(t); };
7
8 struct _Foo {
9 decltype(auto) operator()(auto&& t) {
10 if constexpr (__adl_foo<decltype(t)>)
11 foo(t); // use ADL to find foo
12 else
13 // ...
14 };
15 } // namespace __foo
16 inline namespace _Cpo {
17 inline constexpr __foo::_Foo foo{};
18 }
19 }
We currently use the following namespaces for the CPO types and poison pills:
std::__compare for all CPOs in <compare>
std::ranges::__swap for std::ranges::swap
std::ranges::__imove for std::ranges::iter_move
std::ranges::__iswap for std::ranges::iter_swap
std::ranges::__access for std::ranges::begin, std::ranges::data, std::ranges::size etc.
The actual CPO objects are all defined in an inline namespace called _Cpo, so for the <compare> CPOs that is std::_Cpo and for the others it is std::ranges::_Cpo.
Copyright notices
When moving code between files, ensure that the relevant copyright notice corresponding to the code is also moved. For example, code from <bits/stl_algo.h> might be covered by the HP and SGI copyright notices, and so should not be moved to another header without those notices.
Extensions that libstdc++ relies on
Most of libstdc++ is written in portable C++, only using language features from the revision of the standard that introduced the library component. For example, std::forward_list was introduced in C++11 and so can use using-declarations and uniform initialization. std::variant was introduced in C++17, so can use 'if constexpr' and fold expressions. In a few cases we do make use of newer language features that are supported by GCC as a conforming extension. The list below tries to document these cases, but might be incomplete.
Variadic templates, inline namespaces, and long long are used unconditionally, even in C++98 code.
__decltype is used in C++98 code.
__constinit is used in C++11 code (but only in the library, not in the public headers).
A default member initializer is used in __gnu_cxx::__mutex in C++98.
Some attributes such as [[__likely__]], [[__unlikely__]], and [[__nodiscard__]] are used in C++11 code, before the corresponding standard attribute (without the underscores) was available.
if constexpr is used in C++11 and C++14 code (with appropriate use of diagnostic pragmas to disable -Wc++17-extensions warnings).
We also use the following extensions which are not defined in any revision of the C++ standard:
__complex__ keyword.
#include_next
Reopening inline namespaces without the inline keyword.
- Anonymous structs as data members.
Doxygen
Try to add doxygen comments for everything, even if just a one-liner like:
/// A class that represents a calendar year.
Use the following Doxygen commands to add relevant details:
@param to describe function parameters.
@tparam to describe template parameters.
@returns to describe the return value (and type, if not obvious from the function declaration).
@pre to state preconditions.
@since for components added after C++03, e.g. @since C++17
@headerfile to document which header defines a component, e.g. @headerfile utility (this is not necessary in the <utility> header itself, but is necessary in sub-headers that it includes, e.g. <bits/stl_pair.h>).