Learn how to format C++ code with clang-format in VS Code and apply one consistent style to an entire source tree on Linux

Vivek Bhadra  |  C++ Linux VS Code clang-format

Introduction

Formatting C++ code manually can be both tedious and inconsistent. Even when everyone in a development team follows the same general coding style, small differences gradually begin to appear. One developer may place an opening brace on the same line, another may place it on the following line, and long function calls may be wrapped differently in different files.

These differences do not normally prevent the code from compiling, but they make the codebase less consistent and more difficult to review. A pull request containing a small functional change can become cluttered with unrelated changes to spacing, indentation and line wrapping.

This is where clang-format is useful. It formats C and C++ source code automatically according to a set of rules defined by the project. In this step-by-step Linux tutorial, I will show how I use a .clang-format file in the top-level source directory and a VS Code task in .vscode/tasks.json to format either the active file or an entire C++ project.

A small but important naming detail: the VS Code file is called tasks.json, with an “s”. It must be placed inside the top-level .vscode directory.

What Is clang-format?

clang-format is an automatic source-code formatting tool from the LLVM project. It supports C, C++ and several other languages. For C++ development, it can consistently apply project rules for indentation, braces, line wrapping, pointer alignment, include ordering and many other aspects of source-code layout.

The formatter can be run directly from a Linux terminal, from an editor such as VS Code, or as part of a continuous integration pipeline. The project configuration normally lives in a file called .clang-format. You can find the complete list of supported options in the official clang-format documentation.

Version note: some of the options in this article, in particular PackConstructorInitializers, require clang-format version 14 or newer, and are only reliably honoured from version 17 onwards. If you are on an older release, either upgrade the formatter or expect the constructor-initializer formatting to differ. Check your version with clang-format --version.

Why Is Automatic C++ Code Formatting Needed?

Consider the following deliberately untidy C++ code:

#include <iostream>
#include <string>

class Employee {
public:
Employee(std::string name,int id):m_name{name},m_id{id}{}
void print()const{std::cout<<"Employee: "<<m_name<<", ID: "<<m_id<<"\n";}
private:
std::string m_name;
int m_id;
};

int main(){Employee employee{"Alex",101};employee.print();return 0;}

The code may compile, but its structure is unnecessarily difficult to follow. After applying the formatting rules used in this article, it becomes:

#include <iostream>
#include <string>

class Employee
{
public:
    Employee(std::string name, int id)
        : m_name{name}
        , m_id{id}
    {
    }

    void print() const
    {
        std::cout << "Employee: " << m_name << ", ID: " << m_id << "\n";
    }

private:
    std::string m_name;
    int m_id;
};

int main()
{
    Employee employee{"Alex", 101};
    employee.print();

    return 0;
}

The class, constructor, member variables and function calls are now much easier to identify. More importantly, every file in the project can follow the same style without relying on developers to arrange the code manually.

Automatic formatting provides several practical benefits:

  • consistent formatting throughout the codebase;
  • less time spent adjusting whitespace and indentation;
  • cleaner Git commits and pull requests;
  • fewer style-related discussions during code reviews;
  • easier onboarding for new developers; and
  • a coding style that can also be checked by a CI pipeline.
What clang-format does not do: it does not improve the design, logic or correctness of a program. It consistently arranges the source code according to the rules you provide.

Recommended C++ Project Structure

In my setup, both configuration files live beneath the top-level source directory:

my-cpp-project/
├── .clang-format
├── .vscode/
│   └── tasks.json
├── include/
│   └── Employee.hpp
├── src/
│   ├── Employee.cpp
│   └── main.cpp
├── tests/
│   └── EmployeeTests.cpp
└── CMakeLists.txt
File Purpose
.clang-format Defines how the C and C++ code should be formatted.
.vscode/tasks.json Defines convenient VS Code commands for building and formatting the source code.

Keeping .clang-format at the top level allows the same rules to be discovered from files inside src, include, tests and other subdirectories. When -style=file is used, clang-format searches upwards from the source file until it finds the configuration.

One configuration per project root: clang-format uses only the nearest .clang-format it finds when searching upwards; it does not merge several files together. If a subdirectory contains its own .clang-format, that file takes precedence for the sources beneath it. When formatting does not match your expectations, confirm which configuration is actually in effect with clang-format --style=file -dump-config <source-file>.

How to Format C++ Code with clang-format and VS Code on Linux

1

Install clang-format

On Ubuntu or another Debian-based Linux distribution, install clang-format using:

sudo apt update
sudo apt install clang-format

Then check the installed version:

clang-format --version
If a project standardises on a particular release, install and invoke that specific version, for example clang-format-17. Different versions can occasionally produce slightly different formatting. Because this article uses PackConstructorInitializers, version 17 or newer is recommended. To install a specific version on Ubuntu, use, for example, sudo apt install clang-format-17 and then invoke it as clang-format-17.
2

Create a .clang-format file for the C++ project

Create .clang-format in the top-level source directory. The following is the configuration used in my setup:

---
Language: Cpp
BasedOnStyle: Microsoft
Standard: Latest

# Indentation
IndentWidth: 4
TabWidth: 4
UseTab: Never
ContinuationIndentWidth: 4
ConstructorInitializerIndentWidth: 4
AccessModifierOffset: -4
NamespaceIndentation: None

# Line length and wrapping
ColumnLimit: 100
BinPackArguments: false
BinPackParameters: false
AllowAllArgumentsOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: false
AlignAfterOpenBracket: Align

# Constructor initializer lists: one member per line
BreakConstructorInitializers: BeforeComma
PackConstructorInitializers: Never

# Braces and short statements
BreakBeforeBraces: Allman
Cpp11BracedListStyle: true
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false

# Pointers, references and expressions
DerivePointerAlignment: false
PointerAlignment: Left
ReferenceAlignment: Left
SpaceBeforeParens: ControlStatements
BreakBeforeBinaryOperators: NonAssignment

# Includes and comments
SortIncludes: CaseSensitive
IncludeBlocks: Regroup
FixNamespaceComments: true
ReflowComments: true

# Keep manually arranged tables and consecutive declarations readable.
AlignConsecutiveAssignments: None
AlignConsecutiveDeclarations: None
AlignConsecutiveMacros: None
...
Why these two constructor options matter: without PackConstructorInitializers: Never, clang-format falls back to the packing behaviour inherited from BasedOnStyle: Microsoft and crams several initializers onto each line to fill the column limit. Setting it to Never places one initializer per line, and BreakConstructorInitializers: BeforeComma produces the leading-comma layout shown in the example above. Note also that the closing ... must remain the final line of the file, with every option above it; keys placed after ... are ignored and will cause an “unknown key” error on newer versions.

What Do These Rules Mean?

Setting Effect
BasedOnStyle: Microsoft Uses the Microsoft style as a starting point before applying the remaining overrides.
IndentWidth: 4 Uses four spaces for each indentation level.
UseTab: Never Uses spaces rather than tab characters.
ColumnLimit: 100 Attempts to wrap lines that extend beyond 100 characters.
BreakBeforeBraces: Allman Places opening braces on a new line.
BinPackArguments: false Prevents several wrapped arguments from being packed onto the same line.
PackConstructorInitializers: Never Places each constructor initializer on its own line instead of packing them together.
BreakConstructorInitializers: BeforeComma Breaks before the comma, producing a leading-comma initializer list.
PointerAlignment: Left Formats a pointer as Widget* pointer.
ReferenceAlignment: Left Formats a reference as const Widget& widget.
SortIncludes: CaseSensitive Sorts include directives using case-sensitive comparison.
FixNamespaceComments: true Adds or corrects comments at the end of namespaces where appropriate.
3

Test the configuration on one file

Before formatting the whole project, display the formatted version of one file without modifying it:

clang-format -style=file src/main.cpp

If the output looks correct, apply it to the file:

clang-format -i -style=file src/main.cpp

The -i option edits the file in place. Review the result with:

git diff -- src/main.cpp
4

Add a VS Code task to format the active C++ file

Your .vscode/tasks.json can contain build tasks as well as formatting tasks. The following task formats only the file currently active in VS Code:

{
    "label": "C++: Format active file",
    "type": "process",
    "command": "clang-format",
    "args": [
        "-i",
        "-style=file",
        "${file}"
    ],
    "options": {
        "cwd": "${workspaceFolder}"
    },
    "problemMatcher": [],
    "presentation": {
        "clear": true,
        "echo": true,
        "focus": false,
        "panel": "shared",
        "reveal": "silent"
    },
    "detail": "Format the active source file using the workspace .clang-format configuration."
}

Here, ${file} represents the file currently open in the editor, while ${workspaceFolder} represents the top-level directory opened in VS Code.

5

Add a VS Code task to format the entire C++ project

The active-file task is convenient during normal development, but it does not format every source file. Add the following object to the same tasks array to recursively format the complete project:

{
    "label": "C++: Format entire source tree",
    "type": "process",
    "command": "find",
    "args": [
        "${workspaceFolder}",
        "-type",
        "f",
        "(",
        "-name",
        "*.c",
        "-o",
        "-name",
        "*.cc",
        "-o",
        "-name",
        "*.cpp",
        "-o",
        "-name",
        "*.cxx",
        "-o",
        "-name",
        "*.h",
        "-o",
        "-name",
        "*.hh",
        "-o",
        "-name",
        "*.hpp",
        "-o",
        "-name",
        "*.hxx",
        ")",
        "-not",
        "-path",
        "${workspaceFolder}/build/*",
        "-not",
        "-path",
        "${workspaceFolder}/third_party/*",
        "-not",
        "-path",
        "${workspaceFolder}/external/*",
        "-exec",
        "clang-format",
        "-i",
        "-style=file",
        "{}",
        "+"
    ],
    "options": {
        "cwd": "${workspaceFolder}"
    },
    "problemMatcher": [],
    "presentation": {
        "clear": true,
        "echo": true,
        "focus": false,
        "panel": "shared",
        "reveal": "silent"
    },
    "detail": "Recursively format all C and C++ source files using the workspace .clang-format configuration."
}

This task uses the Linux find command to locate common C and C++ source and header extensions. The expression:

"-exec",
"clang-format",
"-i",
"-style=file",
"{}",
"+"

passes the files to clang-format in groups. Unlike a fragile shell command built from unquoted filenames, this handles paths containing spaces correctly.

The task excludes several directories that normally should not be reformatted:

  • build, which may contain generated compiler output;
  • third_party, which may contain externally maintained dependencies; and
  • external, which may contain other code not owned by the project.
Adjust the exclusion list to match your repository. You may also need to exclude directories such as vendor, generated or an out-of-source build directory with a different name.
6

Run the clang-format task in VS Code

To format the project:

  1. Open the top-level project directory in VS Code.
  2. Press Ctrl+Shift+P to open the Command Palette.
  3. Select Tasks: Run Task.
  4. Select C++: Format entire source tree.

You can also select Terminal → Run Task from the VS Code menu.

VS Code tasks are project commands stored in tasks.json. Further details are available in the official VS Code tasks documentation.

The active-file and whole-project tasks can coexist. Use C++: Format active file while editing one file and C++: Format entire source tree when you want to apply the style across the complete project.

Always Review the Formatting Result

After formatting, inspect the changes before committing them:

git status
git diff --stat
git diff

This is especially important when introducing clang-format into an established project because the first run may modify a large number of files.

Keep Formatting Separate from Functional Changes

If you need to reformat an existing codebase, it is usually better to create a dedicated formatting commit. Mixing widespread formatting changes with a bug fix or new feature makes the functional change much harder to review.

Commit the Configuration Files

Both .clang-format and .vscode/tasks.json should normally be committed to the Git repository. Everyone working on the project can then use the same rules and the same convenient commands.

Use the Same clang-format Version

Where possible, developers and the CI pipeline should use the same version of clang-format. This prevents different tool versions from repeatedly changing the same lines in slightly different ways. It also avoids surprises with newer options such as PackConstructorInitializers, which behave differently on older releases.

Frequently Asked Questions

Where should the .clang-format file be placed?

Place it in the top-level directory of the C++ project. When -style=file is used, clang-format searches upwards from each source file and uses the nearest configuration it finds.

My constructor initializers are still packed onto one line. Why?

This almost always means one of two things. Either the configuration actually in effect is not the one you edited — a different .clang-format higher or lower in the directory tree is taking precedence — or your version of clang-format predates reliable support for PackConstructorInitializers: Never (version 17 or newer is recommended). Confirm the effective configuration with clang-format --style=file -dump-config <source-file> | grep -i PackConstructor, and check the version with clang-format --version.

What is the difference between tasks.json and settings.json?

.vscode/tasks.json defines commands that you run as VS Code tasks. .vscode/settings.json controls editor behaviour, such as formatting the current file automatically when it is saved. A task is particularly useful when you want to format the entire C++ source tree in one operation.

Can clang-format format an entire C++ project?

Yes. clang-format accepts multiple files, but it does not independently discover every C++ file in a directory tree. On Linux, the find command can locate the required source and header files and pass them to clang-format, as shown in the VS Code task above.

Should build and third-party directories be formatted?

Normally, no. Build directories may contain generated files, while third-party directories contain code maintained elsewhere. Excluding these directories prevents unnecessary or unwanted changes.

Does clang-format change how the C++ program works?

It is designed to change the layout of the source code rather than its behaviour. Nevertheless, formatting changes should always be reviewed with git diff before they are committed.

Conclusion

A consistent coding style makes a C++ codebase easier to read, review and maintain. Rather than relying on every developer to format code manually, clang-format turns the agreed style into a repeatable project configuration.

In this Linux and VS Code setup, .clang-format defines the C++ coding style, while .vscode/tasks.json provides convenient commands for formatting either the current file or the complete source tree. Once these files are committed to the repository, any developer can open the project in VS Code and apply the same formatting rules with a single task.

Source Code

The complete C++ sample project, including the .clang-format file, VS Code tasks, CMake configuration and tests, is available on GitHub.

GitHub repository
vivekbhadra/cpp-clang-format-vscode
Clone with SSH: git@github.com:vivekbhadra/cpp-clang-format-vscode.git
View Source Code

Discover more from Tech For Talk

Subscribe to get the latest posts sent to your email.

Leave a Reply