Token Pasting (##) and Stringification (#) in C++

The C++ preprocessor offers metaprogramming capabilities that execute during translation phase 4, well before the compiler analyzes the code's syntax and semantics. Among its most distinct features are the stringification operator (#) and the token-pasting operator (##). This article explains how the preprocessor evaluates these two operators, the mechanics of turning tokens into string literals, how adjacent tokens merge to form new language constructs, and the importance of macro argument prescan.

The Stringification Operator (#)

The stringification operator is a unary operator represented by # that operates exclusively on parameters within function-like macro definitions. When applied to a macro parameter, it converts the corresponding incoming argument into an ordinary string literal.

How It Works

When the preprocessor encounters #parameter during macro replacement:

  1. Token Conversion: It takes the exact sequence of preprocessing tokens provided as the argument and encloses them in double quotes (").
  2. Whitespace Normalization: Leading and trailing whitespaces of the argument are removed. Any internal sequence of multiple whitespace characters is compressed into a single space.
  3. Character Escaping: Any double quotes (") or backslashes (\) occurring within the argument are automatically preceded by an escape backslash (\) to maintain proper string literal syntax.
#define TO_STRING(x) #x

const char* str1 = TO_STRING(int main());   // Expands to: "int main()"
const char* str2 = TO_STRING(He said "Hi"); // Expands to: "He said \"Hi\""

The Token-Pasting Operator (##)

The token-pasting (or concatenation) operator is a binary operator represented by ##. It combines the token preceding it with the token following it, merging them into a single, new preprocessing token.

How It Works

During macro expansion, the preprocessor strips the ## operator and any surrounding whitespace, taking the tokens directly on the left and right sides and fusing them together:

#define MAKE_VAR(prefix, id) prefix##id

int MAKE_VAR(var_, 1) = 42; // Expands to: int var_1 = 42;

Constraints and Undefined Behavior

The concatenated result must be a valid C++ preprocessing token (such as an identifier, keyword, numeric literal, or operator). If the concatenated tokens do not form a valid token, the behavior is undefined:

#define BAD_PASTE(a, b) a##b
BAD_PASTE(123, abc) // Valid: forms the pp-number token 123abc
BAD_PASTE(., >)     // Undefined behavior: .> is not a valid token

Token pasting cannot occur at the very beginning or end of a macro replacement list; a ## operator must always have operands on both sides.

The Macro Prescan Rule and Indirection

A critical aspect of both # and ## is how they affect the evaluation order of macro arguments.

Normally, the preprocessor uses an argument prescan: it fully expands all arguments before replacing them into the macro body. However, operands directly attached to # or ## are exempt from this prescan. Their raw token forms are stringified or pasted immediately, without expanding any macros they might represent.

#define VERSION 2
#define GLUE(a, b) a##b
#define STRINGIFY(x) #x

GLUE(var_, VERSION)    // Expands to: var_VERSION (not var_2)
STRINGIFY(VERSION)     // Expands to: "VERSION" (not "2")

Solving with Two-Level Macro Indirection

To expand macro arguments before stringifying or pasting them, an additional layer of macro indirection is required. The outer macro forces the argument to undergo prescan and expansion before handing it off to the inner macro containing # or ##:

#define GLUE_IMPL(a, b) a##b
#define GLUE(a, b) GLUE_IMPL(a, b)

#define STRINGIFY_IMPL(x) #x
#define STRINGIFY(x) STRINGIFY_IMPL(x)

#define VERSION 2

int GLUE(var_, VERSION) = 10;     // Expands to: int var_2 = 10;
const char* v = STRINGIFY(VERSION); // Expands to: const char* v = "2";

Because VERSION is not adjacent to # or ## in the calling macro (GLUE or STRINGIFY), the preprocessor fully expands VERSION to 2 during the prescan, which is then passed to the implementation macros.