More trailing commas
- Document number:
- P3776R1
- Date:
2025-09-09 - Audience:
- EWG
- Project:
- ISO/IEC 14882 Programming Languages — C++, ISO/IEC JTC1/SC22/WG21
- Reply-to:
- Jan Schultke <janschultke@gmail.com>
- Co-authors:
- Murat Can Çağrı <cancagri.dev@gmail.com>
- GitHub Issue:
- wg21.link/P3776/github
- Source:
- github.com/eisenwave/cpp-proposals/blob/master/src/more-trailing-commas.cow
Contents
Revision history
Changes since R0
Introduction
Recent history
Trailing commas in other languages
Motivation
Improved text editing
Improved version control
Code generation convenience
Improved auto-formatter control
Improved language consistency
Eliminating some uses of __VA_OPT__
Preventing string joining bugs
Motivating examples
Design
New trailing commas
Addressing ambiguity concerns
Already supported trailing commas
Not proposed trailing commas
Commas proposed in P0562R2
Trailing commas in semicolon-terminated lists
Non-lists
Commas following ellipsis parameters
Commas in macros
Comparison to P0562R2
Addressing criticisms
Aesthetic objections
Concerns over C compatibility
Making previously ill-formed code valid, possibly inviting bugs
Semantic inconsistencies with macros
Multiple ways to do the same thing
Claiming syntax space
Implementation experience
Impact on existing code
Wording
Acknowledgements
References
1. Revision history
1.1. Changes since R0
- cited EWG polls in §2.1. Recent history
- expanded and updated §2.2. Trailing commas in other languages
- expanded §3.3. Code generation convenience, referenced [P3294R2]
- elaborated on ClangFormat options in §3.4. Improved auto-formatter control
- added §3.7. Preventing string joining bugs
- reworded §4.4. Comparison to P0562R2
- expanded §5. Addressing criticisms based on EWG reflector discussion
- added Clang fork as §6. Implementation experience
- added feature-test macro to §8. Wording
- added §9. Acknowledgements
- various minor/editorial changes
2. Introduction
C++ permits the use of trailing commas in some lists, but not in others.
For example, trailing commas are permitted at the end of an
For example, the following should be valid:
2.1. Recent history
While this proposal is new,
a very similar proposal [P0562R2] has passed through EWG in Tokyo 2024.
[P0562R2] argues in favor of trailing commas
following
Poll: D0562R1 — Initialization List Symmetry: also add support for base class trailing commas.
SF F N A SA 18 10 2 1 0 Result: Consensus
Poll: D0562R1 — Initialization List Symmetry: forward the paper (with the addition of base class trailing comma) to CWG for inclusion in C++26.
SF F N A SA 12 11 4 3 0 Result: Consensus
However, the subsequent revision [P0562R2] was not polled
due to implementability concerns:
a comma after a
Classes are parsed in two phases: first, declarations are parsed, skipping the bodies of member functions and a few other things, and then those delayed parts are parsed in a context where the class is complete.
For most functions, skipping the body in the first pass is easy. There's an open brace, and you can just skip to the close brace. At worst, there's
, and you need to find the end of the last catch handler.
try { The problem is that a
mem-initializer-id can be atemplate-id that refers to a base class, and the identifiers it uses to name the base class might be declared (possibly after the constructor) in the same class — meaning that we've not parsed them yet. Therefore when we see a, we don't know if it's introducing a template argument list. For example:
< struct X { } ; struct Y : X { Y ( ) : A < b < c > ( ) , { // ... } // A, b, and c declared down here somewhere } ; Are we in the body of the constructor yet?
Prior to P0562R2, the answer was no: an open brace preceded by a comma cannot start the constructor body. For this to be valid,
must be a template, and the
b is then the start of the second template argument of
{ . ([…])
A But after P0562R2, the above example seems to become ambiguous, and I'm not sure how an implementation would be able to parse it.
Following these developments in St. Louis 2024-06,
the proposal has seen no activity.
It seems like the baby has been thrown out with the bathwater here,
since the rationale of the proposal is still sound and
this ambiguity does not affect
2.2. Trailing commas in other languages
Various modern programming languages support trailing commas,
not just in initialization of classes or when listing
members,
but also in function parameter lists or function argument lists.
We can see a clear trend:
Language | Trailing commas support |
---|---|
Swift | Added support for trailing commas in parameter lists in 2025 ([SwiftTrailingCommas]). |
Kotlin | Added support for trailing commas in parameter lists in 2020 ([KotlinTrailingCommas]). |
JavaScript | Added support for trailing commas in parameter lists in ECMASCript2017 ([ECMAScriptTrailingCommas]). |
TypeScript | Added support for trailing commas in parameter lists at the same time they was standardized for JavaScript ([TypeScriptTrailingCommas]). |
Rust |
Has always supported trailing commas in parameter lists. Use of trailing commas is recommended by the official style guide ([RustTrailingCommas]). |
Go | Has always made it mandatory to use trailing commas in multi-line parameter lists (presumably to prevent implicit semicolon insertion). |
Python |
Has always permitted the use of trailing commas in function parameter lists.
Trailing commas in form unary tuples.
|
Julia |
Has always permitted the use of trailing commas in function parameter lists.
Trailing commas in form unary tuples.
|
To the best of my knowledge, trailing commas have been well-received by the users of these languages, to the point where they are the default in e.g. the Rust Style Guide. The motivation to have trailing commas in those languages equally applies to C++, and is largely covered in §3.1. Improved text editing and §3.2. Improved version control.
More discussion on this language design choice can also be found at [RedditTrailingCommas] and [OldNewThing].
3. Motivation
While trailing commas don't solve any major safety or performance issue, they improve developer convenience significantly in some ways. Given how many comma-separated lists developers regularly write, this convenience can be noticeable on a daily basis.
3.1. Improved text editing
Advanced text editors typically have commands for cutting/copying whole lines, or let the developer reorder lines via keyboard shortcut. For example, a line can be swapped with the line above with Alt+ ↑ in VSCode. This can result in compiler errors without trailing commas:
When reordering
and
, errors are raised:
If both lines had a trailing comma and C++ permitted that syntax, reordering these lines would not be a problem.
3.2. Improved version control
When an element is appended to a comma-separated list, this means that the previous element needs to receive a separating comma, resulting in menial changes:
With trailing commas, we can turn a three-line change into a one-line change:
This smaller change is not just easier to review,
it also does not pollute the revision history (
3.3. Code generation convenience
Allowing trailing commas makes it easier to generate C++ code,
for similar reasons as in §3.6. Eliminating some uses of
If the trailing comma is permitted, we can simply add a comma after each list element, rather than adding logic to only add it as a separator between elements.
A number of WG21 members have expressed that this code generation convenience is important to them:
In cppfront I resisted supporting trailing commas for a couple of years, despite frequent pleas from users. What finally made me cave and allow redundant trailing commas in all comma-lists was reflection, especially the generation part of reflection: When generating new code, writing the special-case code all the time to not emit a comma at the end is not hard, but it comes up frequently enough to be an irritation (think: most generated function declarations, and a significant minority of function calls which matters because #calls >> #decls). It's just simpler not to have to have to write that comma logic when generating new code... it's like stopping the "water torture" of incessant little drips none of which is significant in isolation but drive you crazy with constant ceaseless repetition.
— Herb Sutter
3.4. Improved auto-formatter control
Many C++ developers auto-format their code using ClangFormat. One powerful feature it has is the ability to control how brace-enclosed lists are formatted via the use of trailing commas.
The version without a trailing comma may be situationally useful by compactifying code, allowing more to fit on screen. The verbose version with a trailing comma is more useful for regularly updated lists, and can be perceived as more organized. It's easy to envision the same example with function argument lists.
Trailing commas express the desire to format over multiple lines, which auto-formatters may utilize. Without support for trailing commas in e.g. function parameter lists, the developer is robbed of their ability to (elegantly) express that desire, since doing so would make the program ill-formed.
at the end of a line
(which forces line breaks to be retained),
or to manually format a section of code with
.
3.5. Improved language consistency
It is generally surprising that C++ only supports trailing commas within a subset of its comma-separated lists. Besides the design not sparking joy, it creates practical problems.
For example, when refactoring code and e.g. converting list-initialization
such as
which already uses trailing commas into direct-initialization
,
the trailing comma introduces a compiler error until it is removed,
which is a mild inconvenience at least.
3.6. Eliminating some uses of __VA_OPT__
When we want to prepend an element to a variadic macro argument,
we may need to use
With an unconditional comma,
would expand to
,
which is currently ill-formed.
A trailing comma in function calls and other lists would allow us to simplify such macros.
is not part of the
3.7. Preventing string joining bugs
When appending a string literal to a list of arguments and forgetting to add a comma, our code may compile, but do the wrong thing:
Rather than passing a four arguments,
the effect is as if the third argument was
,
which is valid and equivalent to
.
Among other causes, this may happen when pasting the line containing
into position.
This bug would be prevented by the use of trailing commas.
Even if we forget a comma after
,
there should already be one after
,
so nothing surprising happens:
Furthermore, our linter or auto-formatter should enforce the use of commas there, making the bug much easier to find.
However, appending lines is more common than such a swap operation,
and as stated, linter or auto-formatter rules may reveal the lack of comma
following
.
3.8. Motivating examples
In discussions of this proposal,
there is often some skepticism as to whether it's useful for specific lists.
For example, I have been told that trailing commas may be useful
in a
For any of these list types, it is plausible that they are long enough to be broken onto multiple lines, and it is plausible that changes will be made to such lists in the future, which makes a trailing comma useful in these lists.
4. Design
I propose to add a trailing comma after a list whenever possible (i.e. no parsing issues are introduced), with the exception of lists terminated by a semicolon. The design can be summed up as:
When enclosed by
,
{ } ,
( ) , or
[ ] , lists can have a trailing comma.
< >
All the following decisions are intended to make that rule of thumb correct.
Cherry-picking individual cases like permitting
but not
would only complicate the situation.
4.1. New trailing commas
The following trailing commas are proposed, and not currently permitted.
typename
While this initially appears like a huge change,
it can be easily worded by incorporating the trailing comma directly
into lists such as
.
This is worth mentioning because a
4.1.1. Addressing ambiguity concerns
Note that all of these proposed cases are safe from parsing ambiguities
encountered by [P0562R2].
That is because a the trailing comma can only be followed by
'
',
'
',
'
', or
'
'.
In the first three cases,
a closing bracket cannot possibly be the beginning of a new element,
only the end whatever encloses list.
Neither '
', '
', nor '
' are prefix unary operators,
so in any case, '
' can only mean one thing.
Furthermore,
cannot be separated into
and '
'
or combined into
(there is no '
' operator in the language),
so no ambiguity can be introduced by having
appear at the end of a list.
4.2. Already supported trailing commas
The following trailing commas are already supported:
4.3. Not proposed trailing commas
4.3.1. Commas proposed in P0562R2
The following two trailing commas are proposed by [P0562R2] and should be added, if at all possible, as a revision of that paper.
While the parsing ambiguity (§2.1. Recent history)
seems limited to a
,
which makes it quite plausible that similar parsing issues could affect that case.
In any case, to keep this paper focused,
I do not propose those two commas.
4.3.2. Trailing commas in semicolon-terminated lists
Two more commas are not proposed because they are in semicolon-terminated lists:
Firstly, for aesthetic reasons, it is unlikely that users would want to write this.
Furthermore, multiple elements in a
Not only is this exotic, it is also more verbose than the status quo, which already allows reordering lines if we instead write:
Overall, permitting trailing commas
in a
4.3.3. Non-lists
The following trailing commas are not proposed because they are not following a list:
While it may be more philosophically consistent to permit a comma within anything that vaguely looks like a function call, there is little motivation for such cases above. To be fair, a trailing comma could yield a minimal one-line change as follows:
However, it seems unlikely that a developer would choose to distribute a no-message
over three lines in the first place.
Overall, these cases just seem to add more work for implementers.
4.3.4. Commas following ellipsis parameters
Another not proposed trailing comma is that after an ellipsis parameter:
...
This decision is largely based on negative feedback in [CoreReflectorDiscussion].
The trailing comma in this place is largely unmotivated because the ellipsis is always
the last element in the list.
Consequently, a
line cannot be reordered with the line before,
and a comma will never have to be inserted after
to accommodate a subsequent element.
4.3.5. Commas in macros
The following trailing commas are not proposed:
The former lacks motivation because it is highly unlikely
that a C++ use would define multi-line parameters to a function-style macro.
Doing so requires each parameter line to be terminated by \
,
among other reasons.
Supporting trailing commas in
would alter the meaning of existing code.
Currently, that expression is providing three arguments to
,
the last of which is empty.
4.4. Comparison to P0562R2
It is worth noting that [P0562R2] did not propose trailing commas in function and template parameters with the following rationale:
Function and template parameters do not allow a final terminating comma, but these do not trouble me in practice. (Arguably any function or template with enough parameters to be much of a maintenance issue seems like it is ripe for refactoring.) This paper does not propose changing function or template parameters.
Note that [P0562R2] argues that additional trailing commas for
This position is poorly motivated and out-of-touch with language development at large.
Trailing commas in function parameter lists have recently been added to
various programming languages (§2.2. Trailing commas in other languages).
In fact, developers write tremendously more function parameter lists and argument lists than
5. Addressing criticisms
5.1. Aesthetic objections
I have unofficially polled the idea of trailing commas on social media prior to drafting this proposal, and a surprising amount of people objected to the idea. Some negative feedback was also given on R0 of this paper on the EWG reflector. A surprising amount of negative feedback revolved around aesthetics, ranging from "it looks ugly" to "this makes me deeply uncomfortable". This is a valid concern. Since trailing commas are foreign to both natural language and mathematical notation, I can sympathize with this position. However, we must keep some things in mind:
- Trailing commas are entirely optional, so if they cause one discomfort, it is possible to avoid them, at least in one's own personal projects and possibly their code at work.
-
Aesthetic perception is heavily influenced by familiarity.
A Python developer likely perceives C++' use of special characters in
or( ! x && y )
as "ugly". To many, it is easy to get used to trailing commas. Since they are intended for multi-line scenarios, there is a fair amount of distance between 'int ( T :: * )
' and e.g.,
anyway.> -
The job of WG21 is to standardize a programming language used by software engineers.
Trailing commas have clear technical benefits laid out in §3. Motivation,
and clear technical benefits should always take precedence over aesthetics.
If we had gotten hung up over the aesthetics of
and^^
, C++26 reflections would have died.[ :: ]
5.2. Concerns over C compatibility
Some concerns have been raised regarding the fact that only C++ but not C would support the use of trailing commas. This was alleged to make writing common C and C++ code harder.
However, the use of trailing commas is entirely optional, so this claim is untrue. It is not a stated design goal of C++ to make every possible line of C++ code valid C as well. Instead, we maintain a common subset that is both valid C and valid C++. If a developer wants to write C/C++-interoperable code, they can simply not use trailing commas.
Furthermore, I intend to follow this proposal up with a WG14 counterpart if it finds consensus. For the sake of C++ compatibility, it is plausible that WG14 would adopt the syntax as well; this has been done with numerous features before.
5.3. Making previously ill-formed code valid, possibly inviting bugs
Some people consider it a benefit that
is currently not allowed,
which may catch mistakes in some scenarios:
Maybe I was typing, distracted, and forgot my last argument? Maybe a macro expanded badly?
However, there are numerous counter-arguments to this concern:
-
Single-line trailing comma uses like
(which are unmotivated, and most likely how such a mistake is spelled) can also be diagnosed by linters and compilers. This would be similar to thef ( 0 , ) only-multiline string option for ESLint Stylistic'scomma-dangle rule. -
C++ is a statically typed language,
so forgetting an argument, whether spelled
orf ( 0 )
, likely results in an error anyway (no matching overload).f ( 0 , ) - We know based on C++ user experience with trailing commas in braced lists as well as user experience from other languages with trailing comma support, that this is not rampant problem. I was unable to find an authoritative style guide for any language that recommends against the use of trailing commas. One would expect such a style guide to exist if trailing commas were a common source of bugs.
Last but not least, it is worth remembering one of C++' design principles (see Design and Evolution of C++ — 4.3 Design Support Rules):
It is more important to allow a useful feature than to prevent every misuse.
5.4. Semantic inconsistencies with macros
With the proposed syntax in function calls,
would be valid for both regular functions and for function-style macros.
The trailing comma has different meaning in those two:
This inconsistency cannot be changed without altering the semantics of the preprocessor, so it is here to stay. However, the syntax of function-style macro expansions and function calls is already inconsistent:
As things stand, function calls need to be "massaged" a bit to also be valid macro expansions,
e.g. via the use of parentheses.
Users who are uncertain about whether
is a macro can choose
not to use a trailing comma; it is entirely optional.
Furthermore, trailing commas in variadic macros "just work":
5.5. Multiple ways to do the same thing
A common and generally good rule of thumb in programming language design is:
Don't introduce multiple ways to do the same thing.
It could be argued that we can already spell parameter lists without a trailing comma, so it is bad to add a second way. However, this rule is not universally and innately true; it is important to understand why one may follow it:
- Multiple ways of doing the same thing makes a language harder to teach.
- If we can already do the same thing a different way, no value is added to the language.
The first motivation hardly applies because C++ already supports trailing commas in some lists. One could argue the language becomes more teachable by fixing this inconsistency and permitting it in all lists. Even if that argument is not accepted, the teaching effort for an optional punctuation character is microscopic.
Secondly, §3. Motivation lays out numerous ways in which trailing commas
add value to the language.
It would be hard to argue that
add no value to the language
because
exist,
or that
adds no value because
exists.
It is fine to have multiple spellings of the same feature
when there are clear technical benefits to both spellings.
There are no hard rules in language design, only trade-offs and rules of thumb.
5.6. Claiming syntax space
Concerns have been expressed that permitting trailing commas would claim
syntax space, so that
could not be given other meaning in the future.
Specifically, empty list elements could be given special meaning,
such as using a default argument:
Firstly, note that
has the effect of passing
a default argument for
, no matter the meaning of a trailing comma.
would be a special case,
but in principle, trailing commas are compatible with these hypothetical semantics.
Secondly, I consider this idea terrible because of how non-descriptive it is;
a user could even unintentionally perform such calls by making a typo '
'
somewhere in the argument list.
Using the syntax
to mean anything other than a function call
with two arguments and a trailing comma would be terribly confusing to developers
accustomed to trailing commas from other languages,
such as JavaScript or Kotlin developers (§2.2. Trailing commas in other languages).
Without a plausible alternative use for the syntax concerns over claiming syntax space have little relevance. Even the people bringing up these syntax space concerns are not confident that this "default argument passing" could find consensus.
6. Implementation experience
This proposal has been implemented by Can Çağrı in a [ClangFork], and can be tested at [CompilerExplorerDemo]. The implementation is a simple parser change of approximately 40 lines of code.
7. Impact on existing code
The proposal does not alter the meaning of any existing code; it only makes previously ill-formed code valid.
8. Wording
In [expr.prim.lambda.capture], change the grammar as follows:
lambda-capture :- capture-default
opt, - capture-list
opt, - capture-default
capture-list,
opt,
In [dcl.pre] paragraph 1, change the grammar as follows:
sb-identifier-list :- sb-identifier
opt, sb-identifier-listsb-identifier,
sb-identifier-list,
In [dcl.fct] paragraph 2, change the grammar as follows:
parameter-declaration-clause :... - parameter-declaration-listopt
- parameter-declaration-list
,
opt... - parameter-declaration-list
...
In [dcl.init.general] paragraph 1, change the grammar as follows:
[…]
braced-init-list :{ initializer-list
opt, } { designated-initializer-list
opt, } { }
initializer-list :- initializer-clause
opt...
opt, initializer-listinitializer-clause,
opt...
initializer-list,
designated-initializer-list :- designated-initializer-clause
opt, designated-initializer-listdesignated-initializer-clause,
designated-initializer-list,
[…]
In [dcl.attr.grammar] paragraph 1, change the grammar as follows:
annotation-list :- annotation
opt...
opt, annotation-listannotation,
opt...
annotation-list,
In [temp.pre] paragraph 1, change the grammar as follows:
template-parameter-list :- template-parameter
opt...
opt, template-parameter-listtemplate-parameter,
opt...
template-parameter-list,
In [temp.names] paragraph 1, change the grammar as follows:
template-argument-list :- template-argument
opt...
opt, template-argument-listtemplate-argument,
opt...
template-argument-list,
If the proposed resolution of [CWGGithub754] at the time of writing has been applied, revert the change to [stmt.expand] as follows:
expansion-init-list :{ expression-listopt, } { }
Add the following table row to [cpp.predefined] [tab:cpp.predefined.ft]:
9. Acknowledgements
I thank Barry Revzin and Matthias Wippich for helping me craft the example in §3.3. Code generation convenience.
I thank Herb Sutter, Mathias Stearn, Matthias Wippich, Lénárd Szolnoki, Alisdair Meredith, Gabriel Dos Reis, and many others for providing detailed feedback on this proposal, greatly improving its quality.