$identifiers
- Document number:
- Dxxxx
- Date:
2026-05-12 - Audience:
- SG22
- EWG
- Project:
- ISO/IEC 14882 Programming Languages — C++, ISO/IEC JTC1/SC22/WG21
- Reply-to:
- Matthias Wippich <[email protected]>
- Co-authors:
- Murat Can Çağrı <[email protected]>
R0 May 2026
Introduction
Proposed change
Why not allow it unconditionally?
Motivation
Linker and toolchain symbol conventions
Linker conventions
Symbol patching
Stricter Domains
Additional identifier namespace
Macros
Generated code
Wording
[lex.name] Identifiers
Acknowledgements
References
Revision history
0.1. R0 May 2026
Original version of the paper.
1. Introduction
One of the oldest and most widely supported extensions to C++ is allowing $ in identifiers. This feature's origins predate both standard C++ and standard C. As GCC notes in its documentation:
In GNU C, you may normally use dollar signs in identifier names. This is because many traditional C implementations allow such identifiers.
Amongst many other compilers this is supported by MSVC, GCC (going back to at least GCC 1.27!), Clang, EDG (might need opt-in), icx and nvc++ (see Compiler Explorer).
In recent years, C has made quite a few changes to what they consider an identifier. The original rules allowed any implementation-defined characters, the revised ones did not. To address this, [WG14 N3145] introduced a carve-out to allow $ anywhere in identifiers as an implementation extension.
In C++ however, the situation is different. The C++ standard does not acknowledge $ in identifiers at all. As a result, the use of $ in identifiers pedantically renders your code ill-formed. In practice, however, almost all implementations support it as a non-standard extension.
2. Proposed change
To align with C and implementation reality, this paper proposes making the use of $ in identifiers conditionally-supported. This would not make $ identifiers portable, but it would at least make them explicitly recognized by the standard when supported by the implementation.
[WG14 N3145] mentions that during discussion it was noted that allowing $ in identifiers is a massive syntactic land-grab. However, due to the popularity of this extension, it seems unrealistic that we can ever use $ for anything that would conflict with $identifiers.
2.1. Why not allow it unconditionally?
GCC notes in its documentation:
However, dollar signs in identifiers are not supported on a few target machines, typically because the target assembler does not allow them.
Examples of such targets are RS/6000, AVR, and MMIX.
In theory, we could mandate $ to be valid in identifiers and ask compilers to work around such problematic targets in some way or another. However, that's a rather big ask - it seems more sensible to instead align the C++ standard with implementation reality and C23.
3. Motivation
Aside from aligning with C and implementation reality, there are a few more motivating uses.
3.1. Linker and toolchain symbol conventions
3.1.1. Linker conventions
Some embedded toolchains expose identifiers containing $ through linker-defined symbols and custom linker conventions.
One such example is armlink from the ARM MDK toolchain:
Symbols that contain the character sequence
$$, and all other external names containing the sequence$$, are names reserved by ARM.
Unfortunately the examples given in armlink's documentation are not valid C++.
With GCC and Clang this can also be addressed by explicitly specifying the name used in assembler code (Compiler Explorer)
However, it seems much cleaner and overall less error-prone (did you notice the typo?) to avoid repetition and instead actually allow such implementations to support dollars in identifiers directly.
Likewise, if you target RISC-V you may have come across before. The same argument as before applies, however it will still result in a reserved identifier due to the double underscore.
3.1.2. Symbol patching
Furthermore, armlink supports $Sub$$ and $Super$$ symbol patterns for patching existing definitions, and this convention is used for startup hooks, C library retargeting, and runtime initializations. For example, RP2040 MDK headers define the following wrapper macros:
As with linker-defined symbols, these examples do not make $ identifiers portable C++. They do, however, show that $ containing identifiers are a common part of documented embedded toolchain practice rather than merely accidental parser behavior.
3.2. Stricter Domains
In some domains, such as defense, aerospace, avionics, and other safety or mission-critical systems, conformance to the C++ International Standard may be a contractual requirement. For example, the requirements for the [TRITON] project, as specified in NATO IFB-CO-13859-TRITON (Unclassified), state:
5.5.6 TRITON software will be developed with any variations from the languages or specifications given below as "Preferred Languages":
C++ [ISO/IEC 14882]
[T1-R1791] TRITON shall comply with the standards and language specifications given in the Description as the Preferred Languages. Any variations from the languages or specifications shall be agreed with the Purchaser
In such environments, using a compiler extension is not merely an engineering portability concern. It can become a compliance issue. Deviations from the contracted obligations may need to be recorded in project coding guidelines, reviewed by QA, justified to the customer or main contractor, and in some cases handled through contract amendments or formal deviation processes. In such stricter domains, the bureaucracy and paperwork required for such changes can easily take months.
Making the use of $ in identifiers conditionally-supported would not make such identifiers portable, nor would it require every implementation to accept them. However, it would change the status of the construct from an unstandardized implementation extension into a construct explicitly recognized by the C++ Standard, when supported by the implementation.
This distinction is useful for stricter environments. A project could specify that it uses C++ as defined by ISO/IEC 14882, together with the conditionally-supported use of $ in identifiers on implementations that document support for it. This is easier to audit, easier to encode in project guidelines, and easier to justify than relying on undocumented or folklore implementation behavior.
3.3. Additional identifier namespace
3.3.1. Macros
Another somewhat common use is in macro identifiers. GitHub search finds more than 5600 uses in code recognized as C++.
For example consider
As noted earlier, using $ in any identifiers that survive until the assembler takes over can be problematic with some targets. Since macros are long gone at that point, using dollars in macros is relatively safe. This essentially gives us another identifier namespace that is visually distinct from regular identifiers.
If used consistently only for macros, this has the benefit of making them visually stand out. We can validate consistent use easily by using a custom clang-tidy check (see example implementation).
Not only does this reduce the chances of name collisions, it also helps when surveying code - after all, macros come with sharp edges.
3.3.2. Generated code
In a similar fashion, dollars in identifiers can be useful for generated code.
While there has always been C++ code generators, with the recent adoption of [P2996] and its limited reification features such as , there is an ever increasing need for ways to avoid name collisions stemming from generated/reified code.
As a result of that, it's more than likely that we'll continue to see all sorts of creative mangling/uglification schemes - (conditionally) allowing the use of $ provides a viable alternative.
For example, consider a simple struct to struct of arrays transformation (adopted from the struct to struct of arrays example in [P2996]):
If we, for some reason, attempt to inherit from this generated type, we might run into collisions.
While this is arguably a naming issue (that could and probably should be instead), these sorts of problems can generally be avoided by uglifying identifiers in generated code. Conditionally allowing dollars gives users a few more options to make those generated names more unique and less likely to collide with user code.
4. Wording
Make the following changes to the C++ Working Draft. All wording is relative to [N5032], the latest draft at the time of writing.
[lex.name] Identifiers
Add a new paragraph after [lex.name] paragraph 1, add $ to
Identifiers [lex.name]
identifier :identifier-start identifier identifier-continue identifier-start :nondigit an element of the translation character set with the Unicode property XID_Startidentifier-continue :digit nondigit an element of the translation character set with the Unicode property XID_Continuenondigit : one of a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z _ $digit : one of 0 1 2 3 4 5 6 7 8 9[Note: The character properties XID_Start and XID_Continue are described by UAX #44 of the Unicode Standard. — end note]
1 The program is ill-formed if an
identifier does not conform to Normalization Form C as specified in the Unicode Standard.[Note: Identifiers are case-sensitive. — end note]
[Note: [uaxid] compares the requirements of UAX #31 of the Unicode Standard with the C++ rules for identifiers. — end note]
[Note: In translation phase 4,
identifier also includes those preprocessing-tokens ([lex.pptoken]) differentiated as keywords ([lex.key]) in the later translation phase 7 ([lex.token]). — end note]2 The inclusion of
$innondigit is conditionally-supported.[Note: Use of
$anywhere in an identifier is allowed if the implementation supports it. — end note]3 The identifiers in Table 4 have a special meaning when appearing in a certain context. [...]
5. Acknowledgements
Thanks to Jan Schultke for the markup language and document generator used for this paper and thanks to all the awesome people who gave feedback on this proposal.