$identifiers

Document #: Dxxxx
Date: 2026-04-26
Project: Programming Language C++
Audience: EWG
Reply-to: Matthias Wippich
<>

Abstract

This paper proposes making $ in identifiers conditionally-supported to align with C23.

Revision history

R0 April 2026

Original version of the paper.

1 Introduction

One of the most popular extensions to C++ is allowing $ in identifiers. For example, this is supported by MSVC ([MSVC documentation]), GCC ([GCC documentation]), 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.

[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 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. ([GCC documentation])

Examples for such targets are [RS/6000] (because of the AIX assembler), [AVR] and [MMIX].

In theory we could mandate $ to be valid in identifiers and ask compilers to work around such problematic assemblers in some way or another. However, the purpose of this paper is to align the standard with the implementation reality and C23.

3 Motivation

Aside from aligning with C, there are a few more motivating uses.

3.1 Linker-defined symbols

Some linkers emit linker-defined symbols with dollars in their identifiers.

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. ([armlink documentation])

Unfortunately the examples given in armlink’s documentation are not valid C++.
extern int Image$$ER_ZI$$Limit; // oops, not a valid identifier
With GCC and Clang this can also be addressed by explicitly specifying the name used in assembler code (Compiler Explorer)
extern int Image_ER_ZI_Limit asm("Image$$ER_ZI$$Limit");

However, it seems much cleaner and less error-prone to avoid repetition and instead actually allow such implementations to support dollars in identifiers directly.

3.2 Macros

Another somewhat common use is in macro identifiers. Github search finds more than 5600 uses in code recognized as C++ ([Github code search])

For example consider
#if MSVC
#   define $inline_never  __declspec(noinline)
#   define $inline_always __forceinline
#else
#   define $inline_never  [[gnu::noinline]]
#   define $inline_always [[gnu::always_inline]] inline
#endif

#define $inline(_opt)     $inline_##_opt
#define $template         template for (auto _ : "")
#define $pi               3
#define $assert(...)                                                        \
    do {                                                                    \
        ((__VA_ARGS__) ? (void)0 : throw std::runtime_error(#__VA_ARGS__)); \
    } while (false)

$inline(always) int foo(int a) {
    $assert(a > 0);
    return a * $pi;
}

$inline(never) int bar(int, char) {
    $template {
        constexpr auto ctx = std::meta::current_function();
        static constexpr auto [... Idx] = std::make_index_sequence<parameters_of(ctx).size()>{};
        return (foo([:variable_of(parameters_of(ctx)[Idx]):]) + ... + 0);
    }
}

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 this point, using dollars in macros is relatively safe.

If used only for macros consistently, this has the benefit of making them visually stand out. Not only does this reduce chances of name collisions, it also helps when surveying code - after all, macros come with sharp edges.

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.

5.11 [lex.name] Identifiers

Add a new paragraph after 5.11 [lex.name]/1

 identifier:
      identifier-start
      identifier identifier-continue
 identifier-start:
      nondigit
      an element of the translation character set with the Unicode property XID_Start
 identifier-continue:
      digit
      nondigit
      an element of the translation character set with the Unicode property XID_Continue
 nondigit: 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: E [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 (5.5 [lex.pptoken]) differentiated as keywords (5.12 [lex.key]) in the later translation phase 7 (5.10 [lex.token]).end note ]

2 The inclusion of $ in nondigit is conditionally-supported.

[ Note: Use of $ anywhere in an identifier is allowed if the implementation supports it.end note ]

5 Acknowledgements

Thanks to Michael Park for the pandoc-based framework used to transform this document’s source from Markdown.

6 References