How std::complex Works in C++

This article provides an overview of the std::complex class template defined in the C++ <complex> header, explaining how the C++ Standard Library models mathematical complex numbers. It details the internal memory layout, template specializations, interoperability guarantees with C arrays and C99 complex numbers, and the core interfaces used for initialization, component access, and arithmetic manipulation.

Type Definition and Specializations

The C++ standard represents complex numbers—values of the form \(a + bi\), where \(a\) is the real part, \(b\) is the imaginary part, and \(i^2 = -1\)—using the class template std::complex<T>. The ISO C++ standard specifies explicit specializations for the three fundamental floating-point types:

  • std::complex<float>
  • std::complex<double>
  • std::complex<long double>

While the primary template can technically be instantiated with custom types satisfying numeric requirements, standard compliance and full algorithmic support (such as transcendentals) are guaranteed primarily for these three floating-point types.

Memory Layout and Interoperability

One of the defining design requirements of std::complex is its binary representation. The standard guarantees that for an object of type std::complex<T>, the real and imaginary parts are stored sequentially in memory:

  1. The real component is stored first at index 0.
  2. The imaginary component is stored immediately adjacent at index 1.

Because of this requirement:

  • A std::complex<T>* can be safely cast or reinterpreted as an array of two elements T[2].
  • An array of std::complex<T> is laid out consecutively as alternating real and imaginary components (real0, imag0, real1, imag1, ...).
  • The layout is strictly binary-compatible with the C99 float _Complex, double _Complex, and long double _Complex types, as well as the C structure layout containing two consecutive elements of type T.

This makes std::complex zero-overhead and directly compatible with high-performance linear algebra libraries (such as BLAS or LAPACK) without requiring data transformation.

Construction and Component Access

Instances of std::complex can be initialized via constructors or user-defined literals introduced in C++14:

#include <complex>

// Constructor initialization
std::complex<double> z1(3.0, 4.0); // 3 + 4i

// Standard literal initialization (requires namespace std::complex_literals)
using namespace std::complex_literals;
auto z2 = 3.0 + 4.0i;

Component access is provided through member and non-member functions:

  • .real() returns or sets the real part.
  • .imag() returns or sets the imaginary part.
  • In C++20 and later, real() and imag() are also marked constexpr.

Mathematical Operations

The <complex> library provides complete operator overloads for standard arithmetic:

  • Addition (+), subtraction (-), multiplication (*), and division (/) work for complex-complex pairs and mixed complex-scalar operations.
  • Relational equality operators (==, !=) compare both components for equality.

Additionally, standard mathematical functions are overloaded for std::complex:

  • Magnitude and Angle: std::abs(z) computes the magnitude (\(\sqrt{a^2 + b^2}\)), std::norm(z) computes the squared magnitude (\(a^2 + b^2\)), and std::arg(z) computes the phase angle.
  • Polar Representation: std::polar(rho, theta) constructs a complex number from magnitude and angle.
  • Conjugation: std::conj(z) yields \(a - bi\).
  • Transcendental Functions: Exponential, logarithmic, power, trigonometric, and hyperbolic functions (e.g., std::exp, std::log, std::sin, std::sqrt) are implemented handling branch cuts defined by the ISO standard.