More trailing commas
- Document number:
- P3776R0
- Date:
2025-08-27 - Audience:
- EWG
- Project:
- ISO/IEC 14882 Programming Languages — C++, ISO/IEC JTC1/SC22/WG21
- Reply-to:
- Jan Schultke <janschultke@gmail.com>
- GitHub Issue:
- wg21.link/P3776/github
- Source:
- github.com/eisenwave/cpp-proposals/blob/master/src/more-trailing-commas.cow
Contents
Introduction
Recent history
Trailing commas in other languages
Motivation
Improved text editing
Improved version control
Improved auto-formatter control
Improved language consistency
Eliminating some uses of __VA_OPT__
Code generation convenience
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
Could this syntax be used for something else?
Addressing criticisms
Aesthetic objections
Concerns over C compatibility
Making previously ill-formed code valid
Semantic inconsistencies with macros
Multiple ways to do the same thing
Implementation experience
Impact on existing code
Wording
References
1. 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:
1.1. Recent history
While this proposal is new,
a very similar proposal [P0562R2] has recently passed through EWG and CWG,
but was ultimately not polled due to implementability concerns.
[P0562R2] argues in favor of trailing commas
following
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
1.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:
Language | Trailing commas support |
---|---|
Rust |
Has always supported trailing commas in. Use of trailing commas is recommended by the official style guide ([RustTrailingCommas]). |
Kotlin | Added support for trailing commas in 2020 ([KotlinTrailingCommas]). |
JavaScript | Support for trailing commas was standardized in ECMASCript2017 ([ECMAScriptTrailingCommas]). |
TypeScript | Added support at the same time it was standardized for JavaScript ([TypeScriptTrailingCommas]). |
C# | No support yet, in part due to concerns regarding tuple syntax, which also uses parentheses ([CSharpTrailingCommas]). |
Swift | No support yet. Concerns regarding tuple syntax have also been raised ([SwiftTrailingCommas]). |
The motivation for trailing commas in those languages equally applies to C++.
Various other languages such as Python, Go, and Julia also support trailing commas in function calls, but are not specially listed in the table above. That table aims to highlight particularly relevant examples with authoritative resources.
2. 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.
2.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.
2.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 (
2.3. 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 compact 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
.
2.4. 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.
2.5. 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
2.6. Code generation convenience
Allowing trailing commas makes it easier to generate C++ code,
for similar reasons as in §2.5. Eliminating some uses of
2.7. 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.
3. 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.
3.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
3.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.
3.2. Already supported trailing commas
The following trailing commas are already supported:
3.3. Not proposed trailing commas
3.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 (§1.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.
3.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
3.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.
3.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.
3.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.
3.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 hypocritical, lacks reason,
and is out-of-touch with language development at large,
where trailing commas in function parameter lists have recently been added to
various programming languages (§1.2. Trailing commas in other languages).
In fact, developers write tremendously more function parameter lists and argument lists than
3.5. Could this syntax be used for something else?
The idea has been floated on some mailing lists to give empty list elements special meaning, such as using a default argument:
Personally, 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 (§1.2. Trailing commas in other languages).
3.6. Addressing criticisms
3.6.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. Note that this was a blind poll without explaining the purpose of the feature beforehand. When asking opponents of the idea why they don't like it, the answers revolved around aesthetics. In one case literally
it looks ugly
Since trailing commas are foreign to both natural language and mathematical notation, I can sympathize with this position. However, we must keep two things in mind:
-
Aesthetic perception is heavily influenced by familiarity.
A Python developer likely perceives C++' use of special characters in
or( ! x && y )
as "ugly". In practice, 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 §2. Motivation,
and clear technical benefits should always take precedence over matters of personal taste.
If we had gotten hung up over
and^^
"looking ugly", C++26 reflections would have died.[ :: ]
3.6.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.
3.6.3. Making previously ill-formed code valid
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?
The problem with this line of reasoning is that there are infinite things that a developer may forget to type and infinite ways in which macros can expand into something unintended. Trailing commas are not particularly inviting to mistakes.
It is also important to keep one of C++'s language design rules in mind (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.
The usefulness of trailing commas clearly outweighs the benefits from this unlikely scenario where a mistake is caught. Making obvious mistakes such as forgetting arguments likely results in a compiler error anyway, with or without comma, unless that forgotten argument has a default argument or an overload with fewer parameters exists.
3.6.4. Semantic inconsistencies with macros
With the proposed syntax in function calls,
would be valid for both regular functions and for function-style macros.
Previously, it would have only been valid if
is a function-style macro,
but the trailing comma would mean that we are passing two arguments, one of which is empty.
This does result in a bit of surprising behavior for non-variadic cases:
This inconsistency cannot be changed without altering the semantics of the preprocessor,
so it is here to stay.
However, it also seems largely inconsequential.
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":
3.6.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, §2. 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.
4. Implementation experience
None.
5. Impact on existing code
The proposal does not alter the meaning of any existing code; it only makes previously ill-formed code valid.
6. 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 as follows:
expansion-init-list :{ expression-listopt, } { }