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

C++ should support trailing commas in more places, such as in function parameter lists.

Contents

1

Revision history

1.1

Changes since R0

2

Introduction

2.1

Recent history

2.2

Trailing commas in other languages

3

Motivation

3.1

Improved text editing

3.2

Improved version control

3.3

Code generation convenience

3.4

Improved auto-formatter control

3.5

Improved language consistency

3.6

Eliminating some uses of __VA_OPT__

3.7

Preventing string joining bugs

3.8

Motivating examples

4

Design

4.1

New trailing commas

4.1.1

Addressing ambiguity concerns

4.2

Already supported trailing commas

4.3

Not proposed trailing commas

4.3.1

Commas proposed in P0562R2

4.3.2

Trailing commas in semicolon-terminated lists

4.3.3

Non-lists

4.3.4

Commas following ellipsis parameters

4.3.5

Commas in macros

4.4

Comparison to P0562R2

5

Addressing criticisms

5.1

Aesthetic objections

5.2

Concerns over C compatibility

5.3

Making previously ill-formed code valid, possibly inviting bugs

5.4

Semantic inconsistencies with macros

5.5

Multiple ways to do the same thing

5.6

Claiming syntax space

6

Implementation experience

7

Impact on existing code

8

Wording

9

Acknowledgements

10

References

1. Revision history

1.1. Changes since R0

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 initializer-list, but not following a mem-initializer-list, capture-list, template-parameter-list, expression-list, in a postfix-expression, etc. It would be convenient if the support for trailing commas was extended to those other lists.

For example, the following should be valid:

void f( int x, int y, // trailing comma here );

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 mem-initializer-list and (following EWG feedback) base-specifier-list, with similar rationale as this proposal.

Poll: D0562R1 — Initialization List Symmetry: also add support for base class trailing commas.

SFFNASA
1810210

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.

SFFNASA
1211430

Result: Consensus

However, the subsequent revision [P0562R2] was not polled due to implementability concerns: a comma after a mem-initializer-list introduces a parsing ambiguity between initializer lists and function bodies; see Richard Smith's explanation at [P0562R2ReflectorDiscussion]:

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 try {, and you need to find the end of the last catch handler.

The problem is that a mem-initializer-id can be a template-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, b must be a template, and the { 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 base-specifier-lists.

2.2. Trailing commas in other languages

Various modern programming languages support trailing commas, not just in initialization of classes or when listing enum 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 (1,) form unary tuples.
Julia Has always permitted the use of trailing commas in function parameter lists. Trailing commas in (1,) 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.

Many more languages support trailing commas at least within braces, such as C++, Java, C#, etc.

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.

The following motivation uses only function parameter lists as motivating examples, but the arguments equally apply to other comma-separated lists.

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:

void f( int x, int y );

When reordering int x, and int y, errors are raised:

void f( int y // syntax error: "int" following "y" int x, // syntax error: trailing comma );

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:

void f( int x, - int y + int y, + int z );

With trailing commas, we can turn a three-line change into a one-line change:

void f( int x, int y, + int z, );

This smaller change is not just easier to review, it also does not pollute the revision history (git blame) for the previous line of code, and it does not introduce easily avoidable merge conflicts.

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 __VA_OPT__. Generating lists is sometimes made easier if each element in a list can be terminated by a comma, rather than adding special handling for the first/last element to ensure no trailing commas in generated code.

Code generation (using the features proposed in [P3294R2]) for a list of function parameters is simplified by trailing commas in C++ as follows:

consteval std::meta::info produce_parameter() { /* ... */ } consteval std::meta::info produce_parameter_list(span<const Parameter> parameters) { auto result = ^^{}; bool first = true; for (const auto& p : parameters) { result = ^^{ \tokens(result) \tokens(first ? ^^{} : ^^{,}) \tokens(produce_parameter(p)) , }; first = false; } return result; }

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.

ClangFormat "disables bin-packing" when a trailing comma is present, as stated in the documentation of TrailingCommaStyle:

vector<int> numbers { LIST_ITEM_A, LIST_ITEM_B, // LIST_ITEM_C }; // column limit here // vector<int> numbers { LIST_ITEM_A, LIST_ITEM_B, LIST_ITEM_C, // trailing comma here };

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.

A possible workaround for ClangFormat is to insert a trailing // at the end of a line (which forces line breaks to be retained), or to manually format a section of code with // clang-format off.

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 {1, 2, 3,} which already uses trailing commas into direct-initialization (1, 2, 3,), 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 __VA_OPT__ in the following way:

#define F(...) f(0 __VA_OPT__(,) __VA_ARGS__) #define L(...) [& __VA_OPT__(,) __VA_ARGS__] {}

With an unconditional comma, F() would expand to f(0,), which is currently ill-formed. A trailing comma in function calls and other lists would allow us to simplify such macros.

While a capture-default like & is not part of the capture-list, a trailing comma should still be permitted following it. Otherwise, the example above would still be broken.

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:

emplace_strings( "fee", "fie", "foe" + "fum" );

Rather than passing a four arguments, the effect is as if the third argument was "foe" "fum", which is valid and equivalent to "foefum". Among other causes, this may happen when pasting the line containing "fum" into position.

This bug would be prevented by the use of trailing commas. Even if we forget a comma after "fum", there should already be one after "foe", so nothing surprising happens:

emplace_strings( "fee", "fie", "foe", + "fum" );

Furthermore, our linter or auto-formatter should enforce the use of commas there, making the bug much easier to find.

A similar bug is enabled by trailing commas: we could swap the last and second-to-last line in a function call, producing the now valid code:

emplace_strings( "fee", "fie", "fum" "foe", );

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 "fum".

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 template-parameter-list, but not in a template-argument-list. However, while some lists are more frequently multi-line, you can come up with a plausible multi-line example for virtually any list:

// template-parameter-list template < input_iterator I, sentinel_for<I> S, weakly_incrementable O, // trailing comma here > copy_result function(I first, S last, O result); // template-argument-list using V = variant< int_least64_t, float, double, char8_t, char16_t, char32_t, bool, string, // trailing comma here >; // function-parameter-list void pretty_print_log( log_level level, string_view message, optional<code_location> location = {}, optional<const error&> error = {}, // trailing comma here ); // function-argument-list auto it = find( ranges::begin(container), ranges::end(container) + (container.size() / 2), // trailing comma here ); // lambda-capture auto callback = [ &, current_index = 0, invocation_count = 0, // trailing comma here ] { ++invocation_count; // ... }; // annotation-list [[ = testing::description("Fuzzing test for ASCII sequences."), = testing::execution::parallel, // trailing comma here ]] void fuzz_ascii() { /* ... */ }

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 array[a,b,] but not [a,b,]{} would only complicate the situation.

4.1. New trailing commas

The following trailing commas are proposed, and not currently permitted.

[: /* ... */ :]<A, B,> // template-argument-list in splice-specialization-specifier []<A, B,>{} // template-parameter-list in lambda-expression [a, b,]{} // capture-list in lambda-introducer d[a, b,] // expression-list in subscript operator f(a, b,) // expression-list in call operator T(0,) // expression-list in function-style cast typename T(0,) // expression-list in function-style cast with typename new (a, b,) T // expression-list in new-placement new T(a, b,) // expression-list in new-initializer template for (int _ : { a, b, }) // expression-list in expansion-init-list auto [a, b,] // sb-identifier-list in structured-binding-declaration T f(a, b,) // parameter-declaration-list in parameter-declaration-clause T x(a, b,); // expression-list in initializer [[=a, =b,]] // annotation-list in attribute-specifier S() : m(a, b,) // expression-list in mem-initializer template<a, b,> // template-parameter-list in template-head template<C<a, b,> T> // template-argument-list in type-constraint template<template<A, B,> concept> // template-parameter-list in concept-tt-parameter T<A, B,> // template-argument-list in simple-template-id operator()<A, B,> // template-argument-list in template-id

While this initially appears like a huge change, it can be easily worded by incorporating the trailing comma directly into lists such as expression-list, since every occurrence of these lists would allow for trailing commas.

Of these, the expression-list in an expansion-init-list seems like a C++26 defect, and a CWG issue has been requested at [CWGGithub754]. Since this appears to be a separate C++26 defect, not a new C++29 feature, this paper's wording does not include that fix.

Trailing commas would also be supported following a capture-default, such as in [&,]. This is worth mentioning because a capture-default is not actually part of a capture-list, but a standalone element within a lambda-capture.

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, operator, cannot be separated into operator and ',' or combined into operator,> (there is no ',>' operator in the language), so no ambiguity can be introduced by having operator appear at the end of a list.

4.2. Already supported trailing commas

The following trailing commas are already supported:

{ a, b, } // initializer-list in braced-init-list { .a=0, .b=0, } // designated-initializer-list in braced-init-list enum { a, b, } // enumerator-list in enum-specifier [[a,b,,,,]] // attribute-list in attribute-specifier

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.

S() : a{}, b{}, // mem-initializer-list in ctor-initializer struct S : A, B, { }; // base-specifier-list in base-clause

While the parsing ambiguity (§2.1. Recent history) seems limited to a mem-initializer-list, a base-specifier-list is similarly followed by {, 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:

int a, b,; // init-declarator-list in simple-declaration struct S { int a, b,; }; // member-declarator-list in member-declaration friend A, B,; // friend-type-specifier-list in friend-type-declaration using a, b,; // using-declarator-list in using-declaration

Firstly, for aesthetic reasons, it is unlikely that users would want to write this. Furthermore, multiple elements in a init-declarator-list are already discouraged by some style guides. Last but not least, the §3. Motivation of this paper focuses on multi-line scenarios. In the aforementioned cases, to make good use of trailing commas, developers would need to write:

int a, b, ; using a, b, ;

Not only is this exotic, it is also more verbose than the status quo, which already allows reordering lines if we instead write:

int a; int b; using a; using b;

Overall, permitting trailing commas in a init-declarator-list or using-declarator-list seems unmotivated, and would create unnecessary work for implementers.

4.3.3. Non-lists

The following trailing commas are not proposed because they are not following a list:

static_assert(true,"",) // static_assert-declaration contract_assert(true,) // assertion-statement = delete("",) // deleted-function-body

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:

static_assert( true, + "message" );

However, it seems unlikely that a developer would choose to distribute a no-message static_assert 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:

void f( int x, ..., // after ... in parameter-declaration-clause );

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:

#define M(x,y,) // identifier-list in control-line M(x,y,) // arguments to a function-like macro

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 M(x,y,) would alter the meaning of existing code. Currently, that expression is providing three arguments to M, 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 mem-initializer-lists make "maintenance" easier. Maintenance equally motivates trailing commas in function parameter lists. However, [P0562R2] holds function parameter lists to a double standard where if you maintain such lists, they are "ripe for refactoring" anyway.

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 mem-initializer-lists, so the former are more deserving of attention.

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:

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 f(0,) 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:

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, f(0,) would be valid for both regular functions and for function-style macros. The trailing comma has different meaning in those two:

void f(int x); #define F(x) f(0,); // OK F(0,); // error: two arguments provided, but only one parameter

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:

f({1,}); // Passing one argument containing an initializer list to a function F({1,}); // Expanding function-style macro with two arguments (the second argument is a closing brace)

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 f is a macro can choose not to use a trailing comma; it is entirely optional. Furthermore, trailing commas in variadic macros "just work":

#define M(...) f(__VA_ARGS__) // expands to f(0, 1, 2,) M(0, 1, 2,);

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:

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 // line comments add no value to the language because /* block comments */ exist, or that 1s adds no value because std::chrono::seconds(1) 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 f(0,) could not be given other meaning in the future. Specifically, empty list elements could be given special meaning, such as using a default argument:

void f(int x = 0, int y = 0); f(, 1); // OK, equivalent to f(0, 1) f(1, ); // OK, equivalent to f(1, 0) f(1,1,); // error: third default argument requested, but only two parameters

Firstly, note that f(1,) has the effect of passing a default argument for y, no matter the meaning of a trailing comma. f(1,1,) 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 f(1,1,) 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-list , sb-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-list , initializer-clause ...opt , initializer-list
designated-initializer-list:
designated-initializer-clause ,opt
designated-initializer-list , designated-initializer-clause , designated-initializer-list

[…]

In [dcl.attr.grammar] paragraph 1, change the grammar as follows:

annotation-list:
annotation ...opt ,opt
annotation-list , annotation ...opt , annotation-list

In [temp.pre] paragraph 1, change the grammar as follows:

template-parameter-list:
template-parameter ...opt ,opt
template-parameter-list , template-parameter ...opt , template-parameter-list

In [temp.names] paragraph 1, change the grammar as follows:

template-argument-list:
template-argument ...opt ,opt
template-argument-list , template-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-list ,opt }
{ }

Add the following table row to [cpp.predefined] [tab:cpp.predefined.ft]:

__cpp_trailing_comma 20XXXXL

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.

10. References

[N5014] Thomas Köppe. Working Draft, Programming Languages — C++ 2025-08-05 https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/n5014.pdf
[P0562R2] Alan Talbot. Trailing Commas in Base-clauses and Ctor-initializers 2024-04-15 https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p0562r2.pdf
[P3294R2] Andrei Alexandrescu, Barry Revzin, Daveed Vandevoorde. Code Injection with Token Sequences 2024-10-15 https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3294r2.html
[CWGGithub754] Jan Schultke. [stmt.expand] Trailing commas in an expansion-init-list should be permitted 2025-08-24 https://github.com/cplusplus/CWG/issues/754
[P0562R2ReflectorDiscussion] [isocpp-core] P0562R2 -- how is a compiler intended to find the end of a constructor definition? 2024-06-27 https://lists.isocpp.org/core/2024/06/15961.php
[CoreReflectorDiscussion] [isocpp-core] P3776R0 Review of grammar changes date = 2025-08-25 https://lists.isocpp.org/core/2025/08/18487.php
[KotlinTrailingCommas] What's new in Kotlin 1.4.0 — Trailing comma 2020-08-17 https://kotlinlang.org/docs/whatsnew14.html#trailing-comma
[ECMAScriptTrailingCommas] Proposal to allow trailing commas in function parameter lists https://github.com/tc39/proposal-trailing-function-commas
[SwiftTrailingCommas] Swift 6.1 Released — Productivity Enhancements 2025-03-31 https://www.swift.org/blog/swift-6.1-released/#productivity-enhancements
[OldNewThing] Raymond Chen. On the virtues of the trailing comma 2024-02-09 https://devblogs.microsoft.com/oldnewthing/20240209-00/?p=109379