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

clang-format — Code Formatting

clang-format for C++ — configuration, style presets, integration with CMake and CI, and common settings explained.

Quick start

bash
# Format a file in-place
clang-format -i src/main.cpp

# Check if file is formatted (exit code 1 if not)
clang-format --dry-run --Werror src/main.cpp

# Format all C++ files
find src -name "*.cpp" -o -name "*.h" | xargs clang-format -i

.clang-format configuration

Place .clang-format in your project root. clang-format searches parent directories for this file.

yaml
# .clang-format — Google style with C++20 enabled
BasedOnStyle: Google
Standard: c++20

# Indentation
IndentWidth: 4            # spaces per indent level (Google default: 2)
TabWidth: 4
UseTab: Never

# Line length
ColumnLimit: 100          # max line length (Google: 80)

# Brace style
BreakBeforeBraces: Attach  # same line as control statements
# Other options: Allman (always new line), Linux, GNU, Mozilla, WebKit, Custom

# Alignment
AlignConsecutiveAssignments: true   # align = in consecutive assignments
AlignConsecutiveDeclarations: false
AlignTrailingComments: false

# Includes
SortIncludes: CaseSensitive
IncludeBlocks: Regroup     # group: own header, system, third-party
IncludeCategories:
  - Regex: '^<.*\.h>'
    Priority: 2            # C headers
  - Regex: '^<.*>'
    Priority: 3            # C++ STL
  - Regex: '.*'
    Priority: 1            # own headers

# Spacing
SpaceAfterCStyleCast: false
SpaceBeforeParens: ControlStatements
SpaceInEmptyParentheses: false
SpacesInAngles: false      # no: list< int >

# Pointers and references
PointerAlignment: Left     # int* p (Left), int *p (Right), int * p (Middle)

# Lambda
LambdaBodyIndentation: Signature

# Template angle brackets
SpaceBeforeTemplateClose: false  # no: list< int >

# Access modifiers
AccessModifierOffset: -4   # unindent public/private/protected

Built-in style presets

bash
# Show what a style looks like
clang-format --style=LLVM --dump-config > .clang-format

# Available presets: LLVM, Google, Chromium, Mozilla, WebKit, Microsoft, GNU
clang-format --style=Google -i src/*.cpp

# Start from preset, override specific options:
clang-format --style="{BasedOnStyle: Google, IndentWidth: 4}" -i file.cpp

Per-file / per-region disabling

cpp
// clang-format off
constexpr int lookup_table[] = {
    1,  2,  4,  8,
    16, 32, 64, 128,
};
// clang-format on

CMake integration

cmake
# Find clang-format and optionally add format target
find_program(CLANG_FORMAT clang-format)

if(CLANG_FORMAT)
    file(GLOB_RECURSE ALL_SOURCE_FILES
        "${CMAKE_SOURCE_DIR}/src/*.cpp"
        "${CMAKE_SOURCE_DIR}/include/*.h"
    )

    add_custom_target(format
        COMMAND ${CLANG_FORMAT} -i ${ALL_SOURCE_FILES}
        COMMENT "Formatting source files"
    )

    add_custom_target(format-check
        COMMAND ${CLANG_FORMAT} --dry-run --Werror ${ALL_SOURCE_FILES}
        COMMENT "Checking source formatting"
    )
endif()
bash
cmake --build build --target format        # format all files
cmake --build build --target format-check  # CI: fail if not formatted

GitHub Actions CI check

yaml
# .github/workflows/format.yml
name: Format Check

on: [push, pull_request]

jobs:
  format:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install clang-format
        run: sudo apt-get install -y clang-format-17

      - name: Check formatting
        run: |
          find src include -name "*.cpp" -o -name "*.h" \
            | xargs clang-format-17 --dry-run --Werror

Pre-commit hook

bash
#!/bin/sh
# .git/hooks/pre-commit — run clang-format on staged files

STAGED=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(cpp|h|hpp|cc|cxx)$')
if [ -z "$STAGED" ]; then exit 0; fi

FAIL=0
for FILE in $STAGED; do
    diff <(clang-format "$FILE") "$FILE" > /dev/null 2>&1 || FAIL=1
done

if [ "$FAIL" -eq 1 ]; then
    echo "Error: clang-format violations found. Run 'make format' to fix."
    exit 1
fi

Common settings explained

OptionValuesEffect
BasedOnStyleGoogle, LLVM, Mozilla...Start from preset
IndentWidth2, 4, 8Spaces per indent
ColumnLimit80, 100, 120, 0Max line length (0 = unlimited)
BreakBeforeBracesAttach, Allman, LinuxWhere { goes
PointerAlignmentLeft, Right, Middleint* vs int * vs int *
SortIncludesfalse, CaseSensitive, CaseInsensitiveAuto-sort #include
AllowShortFunctionsOnASingleLineNone, Inline, Allint f() { return 0; }
AllowShortIfStatementsOnASingleLineNever, WithoutElse, AllIfsAndElseif (x) return;
ReflowCommentstrue/falseWrap long comments

IDE integration

VS Code: install clang-format extension or use the built-in C++ extension. Add to settings.json:

json
{
  "editor.formatOnSave": true,
  "C_Cpp.clang_format_style": "file",
  "C_Cpp.clang_format_fallbackStyle": "Google"
}

CLion: built-in support — Settings → Editor → Code Style → C/C++ → Import clang-format.

Vim/Neovim: use null-ls or conform.nvim with clang-format formatter.


Formatting Concepts, Coroutines, and Modules (C++20/23)

C++20 concepts and requires-clauses have dedicated formatting options that control where constraint annotations land relative to the declaration they constrain.

yaml
# .clang-format — C++20 concepts and requires-clause placement
Standard: c++20

# Where `requires` clause is placed relative to the template head
RequiresClausePosition: OwnLine
# Options:
#   OwnLine        — requires clause on its own line (most readable for complex constraints)
#   WithFollowing  — attached to the declaration line that follows
#   SingleLine     — keep on one line if it fits within ColumnLimit
#   WithPreceding  — attached to the preceding template<> line

# How `requires` expressions are indented inside a requires clause
RequiresExpressionIndentation: OuterScope
# Options:
#   OuterScope  — aligns with the enclosing scope (less nesting)
#   Keyword     — indented relative to the `requires` keyword itself

# Where `concept` declarations break
BreakBeforeConceptDeclarations: Always
# Options:
#   Always  — concept keyword always on its own line
#   Allowed — break only if it doesn't fit
#   Never   — keep on same line as template<>

# Whether the requires clause in a function template is indented
IndentRequiresClause: true

Before (unformatted requires clause):

cpp
template <typename T> requires std::integral<T> && std::copyable<T>
T clamp(T val, T lo, T hi) { return std::min(std::max(val, lo), hi); }

After (RequiresClausePosition: OwnLine, IndentRequiresClause: true):

cpp
template <typename T>
    requires std::integral<T> && std::copyable<T>
T clamp(T val, T lo, T hi) {
    return std::min(std::max(val, lo), hi);
}

Coroutines (co_return, co_await, co_yield) have no dedicated clang-format options — they are treated as regular C++ keywords and format accordingly based on your existing indentation and line-breaking rules. No special configuration is needed.

cpp
// co_await / co_yield format like any other keyword expression
Task<int> fetch_value() {
    auto result = co_await async_read();
    co_return result * 2;
}

C++ modules (export module, import) are formatted naturally by clang-format and require no special options. Crucially, SortIncludes does not reorder module import declarations — import lines are entirely separate from #include preprocessing and are left in their original order.

cpp
// Module interface unit — clang-format leaves import order untouched
export module math.geometry;

import std;                // standard library module
import math.primitives;   // project module — NOT reordered by SortIncludes

Aligning and Packing Options

Consecutive-alignment and constructor-packing options give you fine-grained control over how clang-format handles vertical alignment and line-break decisions in initializer lists and macro definitions.

yaml
# .clang-format — alignment and packing options

# Align values in consecutive macro definitions
AlignConsecutiveMacros:
  Enabled: true
  AcrossEmptyLines: false
  AcrossComments: false

# Align array-of-structs initializers
AlignArrayOfStructures: Right
# Options: None, Left, Right
# Right aligns each column to the right edge (looks best for struct-of-arrays)

# How constructor initializer lists are packed across lines
PackConstructorInitializers: NextLine
# Options:
#   Never          — one initializer per line always
#   BinPack        — pack as many as fit, then overflow to the next line
#   CurrentLine    — try to fit all on the constructor line, else one per line
#   NextLine       — try to fit all on the next line, else one per line
#   NextLineOnly   — always place on next line(s), never on the declaration line

# Whether to break before the comma in constructor initializer lists
BreakConstructorInitializersBeforeComma: false
# true  -> ,Member(val)  style (comma leads each line)
# false -> Member(val),  style (comma trails, default)

AlignArrayOfStructures: Right in action:

cpp
// Before (None)
Point pts[] = {
    {1, 20, 300},
    {40, 5, 60},
    {7, 800, 9},
};

// After (Right) — columns right-aligned for readability
Point pts[] = {
    {  1,  20, 300},
    { 40,   5,  60},
    {  7, 800,   9},
};

PackConstructorInitializers: NextLine vs Never:

cpp
// PackConstructorInitializers: NextLine
Widget::Widget(int w, int h, std::string name)
    : width_(w), height_(h), name_(std::move(name)) {}

// PackConstructorInitializers: Never
Widget::Widget(int w, int h, std::string name)
    : width_(w),
      height_(h),
      name_(std::move(name)) {}

AlignConsecutiveMacros keeps macro definitions vertically aligned:

cpp
// Before
#define PI 3.14159
#define MAX_SIZE 1024
#define APP_NAME "bigcpp"

// After (AlignConsecutiveMacros: Enabled)
#define PI       3.14159
#define MAX_SIZE 1024
#define APP_NAME "bigcpp"

Python-Based Format Scripts

The LLVM project ships helper scripts for diff-aware and directory-recursive formatting that avoid the "reformat everything" problem when working with legacy codebases.

git clang-format formats only the lines touched by your current changes — the most practical tool for incremental adoption:

bash
# Preview what git clang-format would change (does not apply)
git clang-format --diff HEAD~1

# Apply formatting to lines changed since HEAD~1
git clang-format HEAD~1

# Stage and apply in one step (-f forces application even with uncommitted changes)
git clang-format -f HEAD~1

# Format only the staged changes (what's about to be committed)
git clang-format --staged

Use git clang-format in a pre-push hook to enforce formatting only on changed lines — far less disruptive than a full-repo reformat:

bash
#!/bin/sh
# .git/hooks/pre-push — check formatting on changed lines only

BASE=$(git merge-base HEAD origin/main)
OUTPUT=$(git clang-format --diff "$BASE" 2>&1)

if echo "$OUTPUT" | grep -q "^---"; then
    echo "Error: staged changes are not formatted. Run: git clang-format $BASE"
    exit 1
fi

run-clang-format.py (from the LLVM repository) enables parallel, directory-recursive formatting with proper exit codes for CI:

bash
# Download from LLVM
curl -O https://raw.githubusercontent.com/llvm/llvm-project/main/clang/tools/clang-format/run-clang-format.py

# Check formatting recursively across src/ and include/ (parallel by default)
python3 run-clang-format.py -r src/ include/

# Apply in-place with -i
python3 run-clang-format.py -i -r src/ include/

clang-format-diff.py applies formatting decisions directly to a unified diff, letting you reformat only the changed lines of any diff regardless of VCS:

bash
# Format only lines touched in the last commit
git diff HEAD~1 | clang-format-diff.py -p1 -i

# Dry-run check (print what would change without applying)
git diff HEAD~1 | clang-format-diff.py -p1

Team Adoption Strategy

The "big bang" reformat creates a massive commit that permanently corrupts git blame — every line appears to have been last touched by whoever ran clang-format, making archaeology impossible. Two strategies avoid this.

Strategy A: Hide the formatting commit using .git-blame-ignore-revs.

Create the formatting commit, record its SHA, then add it to a blame-ignore file:

bash
# 1. Run the full reformat and commit it
find src include -name "*.cpp" -o -name "*.h" | xargs clang-format -i
git add -u
git commit -m "style: apply clang-format across entire codebase

FORMATTING_COMMIT: this commit is intentionally excluded from git blame.
See .git-blame-ignore-revs."

# 2. Record the SHA in .git-blame-ignore-revs
git log -1 --format="%H  # clang-format reformat $(date +%Y-%m-%d)" \
    >> .git-blame-ignore-revs

# 3. Configure git to use the ignore-revs file automatically
git config blame.ignoreRevsFile .git-blame-ignore-revs

# 4. Commit the ignore-revs file itself
git add .git-blame-ignore-revs
git commit -m "chore: add formatting commit to blame ignore list"

GitHub's web UI and most git clients respect .git-blame-ignore-revs automatically. The git config line sets it locally; your team can either run it manually or add it to a setup script.

bash
# .git-blame-ignore-revs — formatting commits excluded from git blame
# Format: one full SHA per line, comments with #

a1b2c3d4e5f6...  # clang-format reformat 2025-06-01

Strategy B: Incremental adoption with git clang-format.

Never reformat the whole codebase at once. Instead, enforce formatting only on lines touched by each PR:

bash
# In CI: check that PR diff is formatted
BASE=$(git merge-base HEAD origin/main)
git clang-format --diff "$BASE" | grep -q "^---" && {
    echo "Formatting violations in changed lines. Run: git clang-format $BASE"
    exit 1
}

This approach takes longer to converge but eliminates the big-bang blame problem entirely. Over weeks, the codebase gradually conforms without any single disruptive commit.


clang-format in IDEs — Deep Dive

Pinning the clang-format version in your IDE settings prevents silent style drift when the system clang-format is upgraded.

VS Code — full configuration via .vscode/settings.json:

json
{
  "editor.defaultFormatter": "ms-vscode.cpptools",
  "editor.formatOnSave": true,
  "editor.formatOnType": false,
  "C_Cpp.clang_format_style": "file",
  "C_Cpp.clang_format_fallbackStyle": "Google",
  "C_Cpp.clang_format_path": "/usr/bin/clang-format-17",
  "C_Cpp.clang_format_sortIncludes": false
}

C_Cpp.clang_format_path pins the binary so all contributors use the same version regardless of what clang-format resolves to in PATH. Set C_Cpp.clang_format_sortIncludes: false if you manage include ordering through .clang-format directly rather than the extension shortcut.

CLion looks for .clang-format by walking up from the file being edited — the same lookup strategy clang-format itself uses. Configure at two levels:

  • Project level: Settings → Editor → Code Style → C/C++ → "Set from..." → clang-format. This imports your .clang-format into CLion's own formatter so inspections stay in sync.
  • Format on save: Settings → Tools → Actions on Save → "Reformat code". CLion invokes clang-format directly when a .clang-format file is present, not its internal formatter.

If CLion's reformat and clang-format produce different results, ensure "Use clang-format" is explicitly enabled under Settings → Editor → Code Style → C/C++.

Neovim — conform.nvim is the modern approach, replacing null-ls:

lua
-- ~/.config/nvim/lua/plugins/conform.lua
return {
  "stevearc/conform.nvim",
  opts = {
    formatters_by_ft = {
      cpp = { "clang_format" },
      c   = { "clang_format" },
    },
    formatters = {
      clang_format = {
        -- pin a specific binary; omit to use whatever is in PATH
        command = "clang-format-17",
        -- pass extra args if needed (normally reads .clang-format automatically)
        args = { "--style=file", "--fallback-style=Google" },
      },
    },
    format_on_save = {
      timeout_ms = 500,
      lsp_fallback = false,  -- don't fall back to LSP formatting
    },
  },
}

conform.nvim respects the .clang-format file automatically — no args are needed unless you want to override the style or use a non-default binary.


Validating .clang-format With clang-format-diff

clang-format-diff.py is the right tool for legacy codebases where you cannot afford a full reformat but still want to enforce formatting on every new change going forward.

Feed it any unified diff and it applies (or previews) formatting only for the changed line ranges:

bash
# Install (usually ships with clang-format package)
apt-get install clang-format   # includes clang-format-diff

# Preview formatting violations in the current working-tree diff
git diff | clang-format-diff.py -p1

# Apply formatting to changed lines only (in-place)
git diff | clang-format-diff.py -p1 -i

# Check only staged changes (what's about to be committed)
git diff --cached | clang-format-diff.py -p1

# Specify a clang-format binary explicitly
git diff HEAD~1 | clang-format-diff.py -p1 -binary clang-format-17

Use this in CI to block unformatted additions without touching existing unformatted code:

yaml
# .github/workflows/format.yml (diff-based check)
- name: Check formatting on changed lines
  run: |
    BASE=$(git merge-base HEAD origin/main)
    git diff "$BASE" | clang-format-diff.py -p1 > /tmp/format.patch
    if [ -s /tmp/format.patch ]; then
      cat /tmp/format.patch
      echo "Changed lines are not formatted. Apply with: git diff $BASE | clang-format-diff.py -p1 -i"
      exit 1
    fi

The -p1 flag strips the leading a/ and b/ path prefixes that git diff adds, matching file paths to what clang-format-diff expects.


See Also

  • clang-tidy — static analysis and lint checks that complement formatting
  • clangd — language server that uses your .clang-format for in-editor formatting
  • GCC — compiler reference; often paired with clang-format in mixed toolchains
  • CMake — build system integration for format and format-check targets
Edit on GitHubUpdated 2025-01-01T00:00:00.000Z