The angle bracket is the hard part
Every formatter that treats C++ as C with classes trips on the same character. A less-than sign opens a comparison in one line and a template argument list in the next, and only the surrounding types tell you which. Get the call wrong and std::map<std::string, int> table comes back as arithmetic with stray spaces bolted on.
The tokenizer here looks ahead from each < and asks whether a matching close arrives before a statement ends. If a semicolon, a brace, an assignment or a logical operator turns up first, the character stays a comparison. That single rule keeps if (a < b && b > c) intact while still tightening std::vector<int> into shape.
Nested arguments carry a second trap that outlived C++98. In std::map<int, std::vector<int>> the closing pair lexes as a right shift operator, which is why older compilers demanded a space between them. The lexer splits that token back into two closers when a template list is open, so both the modern spelling and the old > > spacing land in the same place.
Before and after, on the same paste
The sample loaded above is a small registry class. Here is the shape of what arrives against what leaves, with Attach braces at 4 spaces.
template<typename T> class Registry{public:explicit Registry(std::string name):name_(std::move(name)){}
void add(const std::string&key,const T&value){items_[key]=value;}
private:std::map<std::string,T> items_;};template <typename T>class Registry {public:explicit Registry(std::string name) : name_(std::move(name)) {}
void add(const std::string &key, const T &value) {items_[key] = value;}
private:std::map<std::string, T> items_;};Reading that output tells you what the rules are. Indentation comes from counted brace depth rather than from whatever whitespace the source arrived with. Access labels step back to the class column. The template header moves to its own line. Member initializer lists keep their colon spacing, and the lambda body opens a normal block instead of collapsing into the argument list.
Match the project, not your taste
Reformatting a file into a style the repository does not use produces a diff where every line changed and none of them meaningfully. Reviewers stop reading, git blame points at you for code you never wrote. Pick the preset the project already follows and treat the rest as a preference argument for a different day.
- LLVMAttach braces, 2 spaces, references bound right, namespace bodies flush left. The default that clang-format ships with, and the safest pick for a fresh repository.
- GoogleLLVM with the star and ampersand bound to the type, so const T& value. Common across Abseil, Protobuf and most Google published C++.
- MozillaBraces break onto their own line at 2 spaces. Gecko sources and much of the Firefox tree read this way.
- WebKit4 space indent, type bound pointers, template headers left on the declaration line. Blink and JavaScriptCore inherit it.
- Qt4 spaces with references bound right and namespace members indented. Matches Qt Creator defaults and most KDE modules.
Switching any single control drops the preset back to Custom. Nothing is stored between visits, so a reload starts from Attach braces at 4 spaces.
Star position is a readability argument, not a style tic
Both spellings compile identically. The disagreement is about which lie is worse. char* a, b; looks like two pointers and declares one pointer plus one plain char, because the star binds to the declarator. Right binding writes it as char *a, b;, which shows the truth. Type binding argues that the pointer belongs to the type in your head and that multiple declarators on one line were the real mistake.
Pick whichever the file already uses. The setting also drives references, so const std::string &name and const std::string& name follow the same choice rather than drifting apart inside one function signature.
A related habit worth knowing about: const T *ptr and T const *ptr mean the same thing, while T *const ptr means something different. East const writers put the qualifier after the type every time so the rule reads right to left with no exception. This page preserves whichever order you wrote instead of rewriting qualifiers.
Where it stops
- No column limit and no line wrapping. A 200 character template instantiation stays on one line.
- Argument lists are never broken across lines or aligned to an opening parenthesis, which is most of what real clang-format spends its time on.
- Braced initializer lists keep the line breaks you wrote, so a lookup table stays as authored.
- Includes are neither sorted nor grouped, and no forward declarations are moved.
- Reference detection is a heuristic.
Mask & bitswith an uppercase left side reads as a declaration and gets spaced like one. - A macro holding half a brace pair breaks depth counting from that point down. The strip under the editors reports the imbalance rather than pretending the result is sound.
- Sources past 2 MB are refused instead of freezing the tab.
The output is meant for reading code that landed in front of you. What goes into a commit belongs to the formatter your repository already runs, because that one owns line breaking and yours will disagree.
Make the repository decide instead
Drop a config at the root, generated from whichever preset you settled on above.
# .clang-format
BasedOnStyle: LLVM
Standard: c++20
IndentWidth: 4
UseTab: Never
ColumnLimit: 100
PointerAlignment: Right
AccessModifierOffset: -4
NamespaceIndentation: All
AlwaysBreakTemplateDeclarations: Yes
AllowShortFunctionsOnASingleLine: Empty
SortIncludes: CaseInsensitiveThen stop arguing about it in review. Format only the lines a commit touches, which keeps history readable on an old codebase:
git diff -U0 --cached | clang-format-diff -p1 -i
clang-format --dry-run --Werror src/*.cpp include/*.hpp
find src -name '*.cpp' -exec clang-format -i {} +The second line is the one worth putting in continuous integration. It changes nothing and exits non zero when a file drifts, so the check fails on the pull request rather than in a rebase three weeks later.
