$identifiers
- Document number:
- P4234R2
- Date:
2026-07-15 - Audience:
- EWG
- SG22
- Project:
- ISO/IEC 14882 Programming Languages — C++, ISO/IEC JTC1/SC22/WG21
- Reply-to:
- Matthias Wippich <mfwippich@gmail.com>
- Co-authors:
- Murat Can Çağrı <cancagri.dev@gmail.com>
$ in identifiers conditionally-supported or supported.
R2 July 2026
R1 June 2026
R0 May 2026
Introduction
Proposed change
Additional Motivation
Linker and toolchain symbol conventions
Linker conventions
Symbol patching
Stricter Domains
Additional identifier namespace
Macros
Generated code
Preprocessing
Other approaches
Revert the changes to the basic character set
Allow $ in identifiers unconditionally
Implementation status
Opt-out usage
CMake
Makefiles
Wording
Wording Option 1 - conditionally consider $ a nondigit
Wording Option 3 - reject in phase 6
Wording Option 4 - allow unconditionally
Acknowledgements
References
Revision history
0.1. R2 July 2026
- added implementation overview
- added other approaches
- added opt-out usage analysis
- removed "Why not allow $ in identifiers unconditionally?" section, as it is now covered in "Other approaches"
- removed wording for option 2, as it is no longer considered
- incorporated feedback from SG16
0.2. R1 June 2026
- summarized the recent core reflector discussion in a section on preprocessing
- updated/added wording for all three options
- some minor corrections
0.3. 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). Some of these implementations make it configurable, for example GCC and Clang both have to enable or disable this feature (which defaults to enable).
Attempts to standardize $ in identifiers in some form or another date back to at least 1987. The minutes of the 19th X3J11 meeting ([WG14 N034] page 20) note:
49. $ in Identifiers
Peyton (87-220) requested that we permit $ in identifiers, and make all such identifiers reserved to the implementor.
Straw vote:
5 permit $ in reserved identifiers
lots no
In 1992 it was clarified that supporting use of $ in identifiers as a conforming extension is permissible ([X3J11/91-008] page 56).
May a standard conforming implementation make characters in its character set that are not in the required source character set identifier characters?
Answer: Yes.
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 (see [WG14 N2836]). To address this, [WG14 N3145] introduced a carve-out to once again allow $ anywhere in identifiers as a conforming extension.
In C++, the situation is slightly 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, which was a conforming extension until C++26. It effectively became a non-conforming extension through the addition of $ to the basic character set (see [P2558]).
2. Proposed change
To align with C and implementation reality, this paper proposes making the use of $ in identifiers conditionally-supported or supported. Conditionally-supported would not make $ in 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.
3. Additional Motivation
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 (8.5.1 of [RISC-V ELF Specification]). The same argument as before applies - however, in this case 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 to allow patching existing definitions. This feature is commonly used for startup hooks, C library retargeting and other situations where an existing symbol cannot be modified. For example, RP2040 MDK headers define the following wrapper macros:
As with linker-defined symbols, there are still some portability concerns. It does 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 and avionics) that deal with 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.
As mentioned before, 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
Using $ in any identifiers that survive until the assembler takes over might 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 another 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. Preprocessing
In a recent core reflector discussion it was noted that "$ in identifiers is conditionally supported" can mean multiple things. Essentially we have 3 options:
- Option 1
$is conditionally anondigit .- Option 2
$is always anondigit , but conditionally supported in identifiers.- Option 3
$is always anondigit , but conversion of anidentifier preprocessing token to anidentifier token in translation phase 6 is conditionally supported if the identifier contains$.
To understand the implications, consider the following code:
| Variant | Option #1 | Option #2 | Option #3 |
$ supported |
✅ expands to |
✅ expands to |
✅ expands to |
$ not supported |
✅ expands to |
❌ rejected cannot form an in translation phase 3 |
✅ expands to |
Of course code such as
will still be rejected if the implementation cannot support $identifiers, regardless of which option we choose - only the diagnostic changes.
| Variant | Option #1 | Option #2 | Option #3 |
$ supported |
✅ | ✅ | ✅ |
$ not supported |
❌ rejected lexed as followed by other preprocessing token $ e.g. |
❌ rejected cannot form an e.g.
|
❌ rejected cannot convert e.g.
|
Among the conditional approaches, the authors prefer option 3 over options 1 and 2, as it is the least user-hostile option.
Unlike the other options, this gets us consistent preprocessing behavior independent of the target's capabilities rather than a silent change in meaning or a hard error. This also simplifies tooling such as syntax highlighters greatly as they no longer have to be aware of target capabilities in order to tokenize correctly.
An implementation attempt for option 3 can be found at Clang PR #200614.
5. Other approaches
5.1. Revert the changes to the basic character set
Since making $ in identifiers a non-conforming extension was an unintended consequence of [P2558], another approach would be to simply revert adding $ to the basic character set or explicitly exclude it in [lex.pptoken] paragraph 1.
Both of these approaches would revert the status of this extension to a conforming extension. However, this seems strictly worse than any of the other options - for the reasons outlined in §3.2. Stricter Domains, acknowledging extensions as conditionally supported is preferable to leaving them as unstandardized implementation extensions. Therefore, this paper does not propose such a change.
5.2. Allow $ in identifiers 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.
Despite the existence of GCC target configurations that do not allow dollars in identifiers by default, one could argue that we should simply mandate $ to be valid in identifiers and ask compilers to work around such problematic targets in some way or another. In principle, this should be no different to handling arbitrary Unicode characters in identifiers.
As demonstrated by Clang, some targets excluded by GCC (such as RS/6000) have no fundamental issue with $ in identifiers (see Compiler Explorer). For AVR clang also supports $ in identifiers by default (see Compiler Explorer).
This essentially changes the choice of whether the opt-out or the opt-in is the conforming option. To recap:
| Option | Opt-in is conforming | Opt-out is conforming |
| Status Quo pre-C++26 | ✅ | ✅ |
| Status Quo C++26 | ❌ | ✅ |
Allow $ in identifiers unconditionally |
✅ | ❌ |
Since almost all compilers support $ in identifiers in C++ mode by default, this would imply next to no changes to implementations if they wish to remain conforming. Due to the ubiquity of this extension, this approach would also match user expectations most closely. For these reasons, and based on the SG16 review, the authors support this option over all other considered ones.
6. Implementation status
In the following table "opt-out behavior" refers to the preprocessing result of a program
with support for dollars in identifiers disabled (explicitly or implicitly).
| Compiler | Default | CLI Option | Opt-Out Behavior |
| Clang | ✅ allowed | |
|
| GCC | ✅ allowed | |
|
| MSVC | ✅ allowed | (only opt-out, disables all MS extensions) |
|
| EDG | ❌ not allowed | (only opt-in) |
|
| nvc++ | documented as not allowed tested as allowed |
(only opt-in) |
N/A |
| icx | ✅ allowed | |
|
| AOCC | ✅ allowed | |
|
With all tested compilers except for NVC++ (which does not seem to have an opt-out), the observed behavior when opting out matches wording option 1. An overview can be found on Compiler Explorer.
7. Opt-out usage
Since most compilers support $ in identifiers by default, we should see lots of use of the opt-out if this extension causes problems for users. However, GitHub code search does not verify this.
7.1. CMake
A search for in CMake files yields only 26 results at the time of writing. This includes mostly old forks of eclipse-omr and eclipse-openj9. Both projects have since removed the opt-out upstream (OpenJ9 and OMR), because it caused issues as both commits note:
Apple patches libstdc++v3 with additional support for dtrace. These dtrace symbols use dollar signs identifiers, and are incompatible with the compile option -fno-dollars-in-identifiers.
In particular, several patched headers (e.g. ) contain declarations such as:
These declarations are incompatible with .
7.2. Makefiles
A search for in Makefiles yields only 187 results at the time of writing. Aside from numerous old forks of OpenJ9 and OMR, many of these results are game decompilation projects (e.g. CLeSeducteur/oot, mzxrules/mm, LLONSIT/Wave-Race-64 and Lrs121/animalforest).
These game decompilation projects appear to share related build infrastructure and might have been generated by the same decompilation tool. It is not entirely clear why that tool added to the build options, as none of the inspected projects rely on $ being tokenized separately from an adjacent identifier in any C or C++ translation units.
These projects do however preprocess assembly sources with a C++ preprocessor. GCC and Clang support this with the language mode , which has different defaults than the C and C++ language modes. In this mode, $ is not treated as a
Reliance on this behavior is not limited to game decompilation projects. For example, the Linux kernel uses $rva(1b) in preprocessed assembly sources, where $ must remain separate for the macro to be expanded. At the same time, the kernel relies on $ being valid in identifiers in C mode for declarations such as $global$ and $$divI.
The inspected uses therefore provide evidence for preserving assembly-specific preprocessing behavior, but do not demonstrate a need to opt-out of $identifiers in C++ mode.
8. Wording
Make the following changes to the C++ Working Draft. All wording is relative to [N5046], the latest draft at the time of writing.
8.1. Wording Option 1 - conditionally consider $ a nondigit
Add a new paragraph after [lex.name] paragraph 1
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 _ see belowdigit : 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 It is implementation-defined whether
$is included innondigit .3 The identifiers in Table 4 have a special meaning when appearing in a certain context. [...]
8.2. Wording Option 3 - reject in phase 6
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 identifiers in Table 4 have a special meaning when appearing in a certain context. [...]
Tokens [lex.token]
token:identifier keyword literal header-name operator-or-punctuator 1 There are six kinds of tokens: identifiers, keywords, literals, header names, operators, and other separators.
2
Identifier tokens that are converted fromidentifier preprocessing tokens containing$are conditionally-supported.
8.3. Wording Option 4 - allow unconditionally
Modify
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
9. Acknowledgements
Massive thanks to Brian Bi for continued feedback and help with the wording of this paper. Thanks to Jan Schultke for the markup language and document generator used for this paper and thanks to all the other awesome people who gave feedback on this proposal.