Analysis of libc++ STL Source Code – type_traits

In the previous section, we explained the concept of SFINAE. Although concepts were introduced in C++20, most of the experimental features in the standard library still utilize the SFINAE principle. Therefore, understanding this concept is key to comprehending the STL source code.

Why start with type traits?

  • Type traits are relatively simple, which can enhance confidence in reading the source code.
  • Type traits utilize almost all idioms found in STL.
  • Familiarity with type traits will aid in understanding containers, strings, and more.

After analyzing the type traits source code, I will briefly explain the utility standard library (which is easy to understand once you grasp type traits). Subsequently, we will discuss the memory standard library, and finally, we will cover containers, associative containers, etc. Additionally, since iterators are closely related to containers and algorithms, I will intersperse iterator source code analysis while discussing containers.

Furthermore, as the current standard library has already implemented features from C++20/23/part of C++26, this discussion will be divided into two main parts: C++17 and C++20.

The current source code analysis only involves C++17. The C++20 source code analysis will be conducted separately after completing the C++17 analysis.

type_traits is one of the header files related to template metaprogramming in C++. The capabilities provided by this header can be referenced at

https://en.cppreference.com/w/cpp/header/type_traits.html

Before analyzing the type_traits source code, we will briefly introduce some concepts related to traits.

What are traits?

Traits represent natural additional properties of template parameters.

  • Traits are also useful when treated as fixed characteristics (i.e., not passed through template parameters).
  • Trait parameters typically have very natural default values (these defaults are rarely overridden, or cannot be overridden at all).
  • Trait parameters are often closely related to one or more primary parameters.
  • Traits are primarily composed of types and constants, rather than member functions.
  • Traits are usually defined in a centralized manner within the traits template.

In template metaprogramming, you will often encounter a concept: type function (template metaprogram function). This is analogous to ordinary functions in the language. Ordinary functions map some states to other states, while template metaprogram functions map some types or constant values to other types or constant values.

If we do not get caught up in formal definitions, we can simply understand traits as type functions.

For example, here is a simple trait:

template<class _Tp>
struct add_const {
    using type = const _Tp;
};

The above trait is part of the C++ standard library, which simply maps the input template parameter to const _Tp.

type_traits Header File

Here are some key implementations from this header file:

#include <__config>
#include <__type_traits/add_cv_quals.h>
#include <__type_traits/add_pointer.h>
#include <__type_traits/add_reference.h>
#include <__type_traits/aligned_storage.h>
#include <__type_traits/aligned_union.h>
#include <__type_traits/alignment_of.h>
#include <__type_traits/common_type.h>
#include <__type_traits/conditional.h>
#include <__type_traits/decay.h>
#include <__type_traits/enable_if.h>
#include <__type_traits/extent.h>
#include <__type_traits/has_virtual_destructor.h>
#include <__type_traits/integral_constant.h>
#include <__type_traits/is_abstract.h>
#include <__type_traits/is_arithmetic.h>
#include <__type_traits/is_array.h>
#include <__type_traits/is_assignable.h>
#include <__type_traits/is_base_of.h>
#include <__type_traits/is_class.h>
#include <__type_traits/is_compound.h>
#include <__type_traits/is_const.h>
#include <__type_traits/is_constructible.h>
#include <__type_traits/is_convertible.h>
#include <__type_traits/is_destructible.h>
#include <__type_traits/is_empty.h>
#include <__type_traits/is_enum.h>
#include <__type_traits/is_floating_point.h>
#include <__type_traits/is_function.h>
#include <__type_traits/is_fundamental.h>
#include <__type_traits/is_integral.h>
#include <__type_traits/is_literal_type.h>
#include <__type_traits/is_member_pointer.h>
#include <__type_traits/is_nothrow_assignable.h>
#include <__type_traits/is_nothrow_constructible.h>
#include <__type_traits/is_nothrow_destructible.h>
#include <__type_traits/is_object.h>
#include <__type_traits/is_pod.h>
#include <__type_traits/is_pointer.h>
#include <__type_traits/is_polymorphic.h>
#include <__type_traits/is_reference.h>
#include <__type_traits/is_same.h>
#include <__type_traits/is_scalar.h>
#include <__type_traits/is_signed.h>
#include <__type_traits/is_standard_layout.h>
#include <__type_traits/is_trivial.h>
#include <__type_traits/is_trivially_assignable.h>
#include <__type_traits/is_trivially_constructible.h>
#include <__type_traits/is_trivially_copyable.h>
#include <__type_traits/is_trivially_destructible.h>
#include <__type_traits/is_union.h>
#include <__type_traits/is_unsigned.h>
#include <__type_traits/is_void.h>
#include <__type_traits/is_volatile.h>
#include <__type_traits/make_signed.h>
#include <__type_traits/make_unsigned.h>
#include <__type_traits/rank.h>
#include <__type_traits/remove_all_extents.h>
#include <__type_traits/remove_const.h>
#include <__type_traits/remove_cv.h>
#include <__type_traits/remove_extent.h>
#include <__type_traits/remove_pointer.h>
#include <__type_traits/remove_reference.h>
#include <__type_traits/remove_volatile.h>
#include <__type_traits/result_of.h>
#include <__type_traits/underlying_type.h>

#if _LIBCPP_STD_VER >= 14
#    include <__type_traits/is_final.h>
#    include <__type_traits/is_null_pointer.h>
#endif

#if _LIBCPP_STD_VER >= 17
#    include <__type_traits/conjunction.h>
#    include <__type_traits/disjunction.h>
#    include <__type_traits/has_unique_object_representation.h>
#    include <__type_traits/invoke.h>
#    include <__type_traits/is_aggregate.h>
#    include <__type_traits/is_swappable.h>
#    include <__type_traits/negation.h>
#    include <__type_traits/void_t.h>
#endif

#if _LIBCPP_STD_VER >= 20
#    include <__type_traits/common_reference.h>
#    include <__type_traits/is_bounded_array.h>
#    include <__type_traits/is_constant_evaluated.h>
#    include <__type_traits/is_unbounded_array.h>
#    include <__type_traits/type_identity.h>
#    include <__type_traits/unwrap_ref.h>
#endif

#if _LIBCPP_STD_VER >= 23
#    include <__type_traits/is_implicit_lifetime.h>
#    include <__type_traits/reference_constructs_from_temporary.h>
#    include <__type_traits/reference_converts_from_temporary.h>
#endif
  • The libc++ standard library places the actual implementation of type_traits in the internal _type_traits directory.
  • Each trait is in a separate header file.
  • type_traits has already implemented some features of C++20 and C++23; I will not analyze the source code here but will do so in a separate section. We will primarily analyze the source code of C++17 and earlier.
  • Each analysis will aim to select 3-5 header files for source code analysis.

In the next section, we will analyze the source code of the first five traits in __type_traits.

Leave a Comment