Skip to content
C++
Analysis & Formatting
Updated 2025-01-01T00:00:00.000Z

Doxygen — C++ Documentation Generator

Doxygen for C++ — comment syntax, Doxyfile configuration, generating HTML/PDF docs, Graphviz call graphs, and CMake integration.

TL;DR

Doxygen generates HTML, PDF, and XML documentation from annotated C++ source comments. Write /// or /** */ comments above declarations; run doxygen to produce browsable docs.


Comment Syntax

cpp
/// @brief Single-line brief description.
/// @param x The input value — must be positive.
/// @return The square root, or -1.0 on error.
/// @throws std::invalid_argument if x is negative.
double safe_sqrt(double x);

/**
 * @brief Multi-line Doxygen comment.
 *
 * Longer description goes here. Can span multiple paragraphs.
 *
 * @tparam T Element type — must satisfy std::totally_ordered.
 * @param data Input range to process.
 * @param threshold Minimum value to include.
 * @return Number of elements above threshold.
 *
 * @see filter_below()
 * @since C++17
 */
template<std::ranges::range R>
size_t count_above(R&& data, std::ranges::range_value_t<R> threshold);

/// @note Not thread-safe. Use external locking if shared across threads.
/// @warning Calling with an empty container is undefined behavior.

Documenting Classes

cpp
/**
 * @class EventBus
 * @brief Decoupled publish/subscribe event dispatcher.
 *
 * Connects publishers and subscribers without direct coupling.
 * All operations are thread-safe.
 *
 * @code
 * EventBus bus;
 * auto token = bus.subscribe<ClickEvent>([](const ClickEvent& e) {
 *     std::println("clicked at {},{}", e.x, e.y);
 * });
 * bus.publish(ClickEvent{10, 20});
 * @endcode
 */
class EventBus {
public:
    /// @brief Subscribe to events of type T.
    /// @tparam T Event type — must be copyable.
    /// @param handler Callback invoked on each published event.
    /// @return Subscription token; cancel by destroying it.
    template<typename T>
    [[nodiscard]] Token subscribe(std::function<void(const T&)> handler);

    /// @brief Publish an event to all subscribers.
    /// @tparam T Event type.
    /// @param event The event to publish.
    template<typename T>
    void publish(T event);
};

Doxyfile Quick Setup

bash
# Generate default Doxyfile
doxygen -g

# Key settings to edit in Doxyfile:
ini
PROJECT_NAME     = "MyProject"
PROJECT_VERSION  = "1.0"
INPUT            = src/ include/
RECURSIVE        = YES
OUTPUT_DIRECTORY = docs/

# Output formats
GENERATE_HTML    = YES
GENERATE_LATEX   = NO        # set YES for PDF

# Diagrams (requires Graphviz dot)
HAVE_DOT         = YES
CALL_GRAPH       = YES
CALLER_GRAPH     = YES
CLASS_DIAGRAMS   = YES
INCLUDE_GRAPH    = YES

# Extract everything (including undocumented)
EXTRACT_ALL      = NO        # NO = only documented, YES = everything
EXTRACT_PRIVATE  = NO
EXTRACT_STATIC   = YES

# Source browser
SOURCE_BROWSER   = YES
INLINE_SOURCES   = NO
bash
# Run
doxygen Doxyfile
# Open docs/html/index.html

CMake Integration

cmake
find_package(Doxygen REQUIRED)

set(DOXYGEN_PROJECT_NAME    "${PROJECT_NAME}")
set(DOXYGEN_PROJECT_VERSION "${PROJECT_VERSION}")
set(DOXYGEN_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/docs")
set(DOXYGEN_RECURSIVE YES)
set(DOXYGEN_EXTRACT_ALL NO)
set(DOXYGEN_HAVE_DOT YES)
set(DOXYGEN_CALL_GRAPH YES)
set(DOXYGEN_GENERATE_HTML YES)
set(DOXYGEN_GENERATE_LATEX NO)

doxygen_add_docs(
    docs                          # target name: cmake --build . --target docs
    ${CMAKE_CURRENT_SOURCE_DIR}/include
    ${CMAKE_CURRENT_SOURCE_DIR}/src
    COMMENT "Generating API documentation"
)

Markdown Pages

markdown
<!-- mainpage.md -->
# MyProject {#mainpage}

Welcome to **MyProject** documentation.

## Quick Start
...
ini
# Doxyfile
USE_MDFILE_AS_MAINPAGE = mainpage.md
INPUT                 += mainpage.md

Common Tags Reference

cpp
@brief          Short one-line description
@details        Extended description
@param name     Function parameter
@tparam T       Template parameter
@return         Return value description
@returns        Alias for @return
@throws type    Exception that may be thrown
@pre            Precondition
@post           Postcondition
@note           Additional note
@warning        Important warning
@see other_fn   Cross-reference
@since version  When introduced
@deprecated     Mark as deprecated
@code / @endcode  Inline code example
@todo           Outstanding work item
@bug            Known bug

Groups and Modules

Groups let you organize your public API into named sections that map to separate pages in the HTML output. Without them, all symbols appear in a flat list sorted by file; with groups, users land on "Math utilities" or "I/O utilities" pages that reflect the design of your library.

Define a group once with @defgroup, then pull any symbol into it with @ingroup:

cpp
/**
 * @defgroup math_utils Math Utilities
 * @brief Fast scalar and vector math helpers.
 */

/**
 * @ingroup math_utils
 * @brief Clamp x to [lo, hi].
 */
template<typename T>
T clamp(T x, T lo, T hi);

Use the @addtogroup block syntax with @{ / @} to add multiple consecutive declarations to a group without repeating @ingroup on every one:

cpp
/// @addtogroup math_utils
/// @{

/// @brief Linearly interpolate between a and b by factor t.
float lerp(float a, float b, float t);

/// @brief Compute the next power of two >= n.
uint32_t next_pow2(uint32_t n);

/// @}

Groups can also be nested — define a child group with @ingroup <parent> in its @defgroup comment. The HTML sidebar renders them as a collapsible tree.

cpp
/**
 * @defgroup io_utils I/O Utilities
 * @brief Stream and file helpers.
 */

/**
 * @defgroup io_binary Binary I/O
 * @ingroup io_utils
 * @brief Low-level binary read/write helpers.
 */

Custom HTML and Theming

Doxygen's default HTML theme is functional but dated; a single CSS override or the Doxygen Awesome theme transforms it into a modern, dark-mode-capable reference site.

Control the color scheme with HTML_COLORSTYLE and inject a stylesheet for fine-grained control:

ini
# Doxyfile
HTML_COLORSTYLE      = AUTO        # LIGHT | DARK | AUTO (follows OS preference)
HTML_EXTRA_STYLESHEET = docs/custom.css
HTML_HEADER          = docs/header.html   # optional — full custom header
HTML_FOOTER          = docs/footer.html   # optional — full custom footer

For a production-quality look, use Doxygen Awesome (github.com/jothepro/doxygen-awesome-css). Download the CSS files alongside your Doxyfile and point Doxygen at them:

bash
# one-time setup
git submodule add https://github.com/jothepro/doxygen-awesome-css docs/doxygen-awesome
ini
# Doxyfile additions for Doxygen Awesome
GENERATE_TREEVIEW    = YES          # required by the theme
DISABLE_INDEX        = NO
FULL_SIDEBAR         = NO
HTML_COLORSTYLE      = LIGHT        # theme handles dark mode via JS

HTML_EXTRA_STYLESHEET  = docs/doxygen-awesome/doxygen-awesome.css \
                         docs/doxygen-awesome/doxygen-awesome-sidebar-only.css

# Optional interactive extensions (copy JS files from the repo):
HTML_EXTRA_FILES       = docs/doxygen-awesome/doxygen-awesome-fragment-copy-button.js \
                         docs/doxygen-awesome/doxygen-awesome-interactive-toc.js

Add the extension scripts in a custom HTML_HEADER so they initialize before page render. The Doxygen Awesome repo ships a ready-made header.html template — copy it and adjust your project name and logo path.


Generating XML for Custom Doc Pipelines

Doxygen's XML output is the bridge to Sphinx and Read the Docs — enable it to feed structured API data into any downstream documentation tool.

ini
# Doxyfile
GENERATE_XML         = YES
XML_OUTPUT           = xml/
XML_PROGRAMLISTING   = YES   # embed source listings in the XML
GENERATE_HTML        = NO    # optional: skip HTML when only XML is needed

Running doxygen now produces xml/index.xml plus one XML file per symbol. The most common consumer is Breathe, a Sphinx plugin that renders Doxygen XML as RST directives.

Install and configure Breathe in your Sphinx project:

bash
pip install breathe
python
# docs/conf.py
extensions = ["breathe"]

breathe_projects = {
    "myproject": "../xml/",   # path to Doxygen XML_OUTPUT directory
}
breathe_default_project = "myproject"

Then reference C++ symbols directly in RST:

rst
.. doxygenclass:: EventBus
   :members:
   :undoc-members:

.. doxygenfunction:: safe_sqrt

.. doxygengroup:: math_utils
   :members:

This workflow is the standard approach for C++ libraries hosted on Read the Docs: Doxygen extracts the API, Breathe bridges the formats, and Sphinx renders the final site with full cross-referencing between narrative docs and API reference.


Documenting Concepts and Ranges (C++20)

Doxygen 1.9.6+ understands C++20 concept declarations natively — earlier versions need a workaround.

On a current Doxygen release, document a concept the same way you document any declaration; the @concept tag is accepted for explicit labeling:

cpp
/**
 * @concept Sortable
 * @brief A type that supports weak ordering and swapping.
 *
 * @tparam T The type to check.
 *
 * Satisfied by any type for which `std::sort` is well-formed on a range of T.
 */
template<typename T>
concept Sortable = std::totally_ordered<T> && std::swappable<T>;

Document requires-clauses on constrained functions with @satisfies to cross-reference which concept a parameter must model:

cpp
/**
 * @brief Sort elements in-place using introsort.
 * @tparam Iter Iterator type.
 * @satisfies Iter must model std::random_access_iterator with a Sortable value type.
 * @param first Begin iterator.
 * @param last  End iterator.
 */
template<std::random_access_iterator Iter>
    requires Sortable<std::iter_value_t<Iter>>
void fast_sort(Iter first, Iter last);

For Doxygen versions older than 1.9.6 that do not parse concept correctly, use @par to document constraints as a named section:

cpp
/**
 * @brief Count elements satisfying a predicate.
 * @tparam R  Input range.
 * @tparam P  Predicate type.
 *
 * @par Requirements
 * - `R` must satisfy `std::ranges::input_range`.
 * - `P` must be invocable with `std::ranges::range_value_t<R>` and return `bool`.
 */
template<std::ranges::input_range R, typename P>
size_t count_if(R&& range, P pred);

Collaborative Documentation — Aliases

Project-wide aliases in the Doxyfile let a team enforce consistent documentation vocabulary without repeating boilerplate in every comment.

Add ALIASES entries to the Doxyfile — values may include Doxygen tags, HTML, and ^^ for a forced newline inside a @par block:

ini
# Doxyfile — shared aliases snippet (commit alongside the Doxyfile)
ALIASES  = "complexity=@par Time complexity^^"
ALIASES += "thread_safe=@note Thread-safe. May be called from multiple threads simultaneously."
ALIASES += "not_thread_safe=@warning Not thread-safe. Protect with an external mutex when shared."
ALIASES += "since_cpp20=@since C++20 — requires \c -std=c++20 or later."
ALIASES += "nodiscard_reason{1}=@note Return value should not be discarded: \1"

With these aliases, any team member can write terse, consistent annotations:

cpp
/// @brief Multiply two matrices.
/// @complexity O(n³) naive; use BLAS for large n.
/// @thread_safe
/// @nodiscard_reason{The product is the only output — ignoring it is almost always a bug.}
[[nodiscard]] Matrix matmul(const Matrix& a, const Matrix& b);

Keep aliases in a separate file (e.g., docs/aliases.dox) and include it with @INCLUDE = docs/aliases.dox so individual Doxyfiles across a monorepo can share the same vocabulary without copying.


Automated API Docs in CI — GitHub Pages

A GitHub Actions workflow can regenerate and publish your API docs on every push to main with zero manual steps.

The workflow has three stages: install Doxygen, run it, then deploy docs/html to GitHub Pages using the official Actions:

yaml
# .github/workflows/docs.yml
name: API Docs

on:
  push:
    branches: [main]
  workflow_dispatch:

permissions:
  contents: read
  pages: write
  id-token: write

concurrency:
  group: pages
  cancel-in-progress: true

jobs:
  build-docs:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          submodules: recursive   # needed if doxygen-awesome is a submodule

      - name: Install Doxygen + Graphviz
        run: sudo apt-get install -y doxygen graphviz

      - name: Cache Doxygen output
        uses: actions/cache@v4
        with:
          path: docs/html
          key: doxygen-${{ hashFiles('Doxyfile', 'include/**', 'src/**') }}

      - name: Generate docs
        run: doxygen Doxyfile
        env:
          # Treat every Doxygen warning as a build error
          DOXYGEN_WARN_AS_ERROR: YES

      - name: Upload Pages artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: docs/html

  deploy-docs:
    needs: build-docs
    runs-on: ubuntu-latest
    environment:
      name: github-pages
      url: ${{ steps.deploy.outputs.page_url }}
    steps:
      - name: Deploy to GitHub Pages
        id: deploy
        uses: actions/deploy-pages@v4

Set WARN_AS_ERROR = YES (or DOXYGEN_WARN_AS_ERROR in the environment) so undocumented parameters and broken @see references surface as CI failures rather than silent omissions. Enable Pages in your repository settings under Settings → Pages → Source → GitHub Actions before the first run.


See Also

  • CMake — build system integration; use doxygen_add_docs() to wire docs into your CMake targets
  • clang-format — keep source code style consistent so Doxygen's source browser renders cleanly
Edit on GitHubUpdated 2025-01-01T00:00:00.000Z