The __COUNTER__ predefined macro

Jens Gustedt, INRIA and ICube, France

Jeremy Rifkin, Aquatic, USA

2025-01-25

target

integration into IS ISO/IEC 9899:202y

document history

document number date comment
n3457 202501 Original proposal

license

CC BY, see https://creativecommons.org/licenses/by/4.0

1 Introduction

The __COUNTER__ predefined macro is a common language extension for C and C++ which expands to an integer literal that starts at 0 and increments by 1 every time it is expanded in a translation unit. This is useful for generating unique identifiers, generating unique indices, and other preprocessor metaprogramming uses. We propose standardizing the macro and its semantics.

1.1 Previous Papers

A standalone paper for __COUNTER__ has not been presented to WG14, however, it was briefly mentioned in, Extensions to the preprocessor for C2Y, n3190. This paper provides a focused proposal on __COUNTER__ and aims to provide additional context and motivation. It originates from a discussion in the C-C++ liaison SG of C++ paper P3384 as submitted to WG21.

2 Motivation

__COUNTER__ is de-facto portable today. Most implementations support it with unsurprising semantics. However, there is inherent uncertainty surrounding portability and semantics due to it not being standardized.

Consequently, codebases striving for maximum portability must resort to detection and fallback such as this example from google benchmark:

// Check that __COUNTER__ is defined and that __COUNTER__ increases by 1
// every time it is expanded. X + 1 == X + 0 is used in case X is defined to be
// empty. If X is empty the expression becomes (+1 == +0).
#if defined(__COUNTER__) && (__COUNTER__ + 1 == __COUNTER__ + 0)
#define BENCHMARK_PRIVATE_UNIQUE_ID __COUNTER__
#else
#define BENCHMARK_PRIVATE_UNIQUE_ID __LINE__
#endif

Meanwhile other C and C++ codebases avoid the macro altogether due to this uncertainty. In the absence of cautious checking and fallback, a developer must consult numerous widely used implementations to convince themselves that __COUNTER__ exists and behaves as they expect.

In the case of google benchmark, __LINE__ is an adequate fallback due to how BENCHMARK macros are typically used. However, this is not an adequate general-purpose replacement due to it not being unique in the general case.

While every major C++ compiler today supports __COUNTER__, it’s not always enabled. For example, EDG only provides it outside of standards mode.

Additionally, minor divergences in __COUNTER__ semantics are observable (see Number of Expansions), though they do not impact most use cases.

Due to fairly widespread use in both C and C++ it would be useful to incorporate the existing practice of __COUNTER__ into the official standard in order to provide more clear portability and semantic guarantees.

3 Motivating Examples

A brief survey of some uses of __COUNTER__ in C and C++:

C:

Many additional uses include use for static assertions, however, that use case is now covered by C11 and C23 static assertion facilities.

C++:

4 Current Compiler Support

All major C and C++ compilers support __COUNTER__ as well as most C compilers available on Compiler Explorer:

Compiler explorer comparison with earliest supported versions available https://godbolt.org/z/Mx4MznMaY

5 Approach

Our suggested wording below takes several points that could present difficulties into account.

5.1 Number of expansions

There are a few edge cases where implementations diverge in what is considered to be an expansion of the __COUNTER__ macro. Currently, without the addition of __COUNTER__ it is not observable if macro parameters are expanded only once (and then inserted in the replacement list) or if such an expansion is performed at each point of insertion. __COUNTER__ changes this, because it introduces a side effect into preprocessing.

We propose a parameter be expanded only once, which is the current behavior of almost all compilers tested on Compiler Explorer:

#define X(Z) Z Z
X(__COUNTER__) // 0 0 (chibicc expands as 0 1)

Similarly, if the parameter is never used no expansion should occur:

#define FOO(X)
FOO(__COUNTER__)
__COUNTER__ // 0

Additionally, __VA_OPT__ should be considered to expand __VA_ARGS__ even if __VA_ARGS__ is unused. This is for ease of implementation and is the current behavior on GCC and MSVC but not Clang:

#define X(...) __VA_OPT__()
X(__COUNTER__)
__COUNTER__ // 1 (clang expands as 0)

Notably, Clang produces the desired output in the following example:

#define X(...) __VA_OPT__(a)
X(__COUNTER__)
__COUNTER__ // 1

The stringizing operator and token pasting operator should not cause expansion. This is the current behavior on all compilers tested on compiler explorer.

#define STR(X) #X
STR(__COUNTER__) // "__COUNTER__"
#define CONCAT(X) A##X
CONCAT(__COUNTER__) // A__COUNTER__
__COUNTER__ // 0
#define CONCAT2(X) A##X X
CONCAT2(__COUNTER__) // A__COUNTER__ 1
__COUNTER__ // 2

We think that the text that regulates expansion of macro parameters should be adapted, such as to remove these ambiguities. This is proposed for clause 6.10.5.2. Here, we now distinguish the parameter name itself more clearly from its potentially multiple appearances in the replacement list and insist that the parameter is expanded at most once, and then spliced into the overall macro expansion at each of its occurrences.

5.2 Maximum value

We propose to proceed analogously to the __LINE__ macro and the #line directive. For the latter a fixed bound for the line number of 231-1 (which is 2147483647) is established, so we just copy that value for the bound on __COUNTER__.

If stepped over, such an upper bound should still not result in new UB for the preprocessor. Since it is easily detectable that the value overflows over the given limit, we constrain implementations to diagnose situations where such an overflow happens.

5.2.1 Design Questions

5.2.1.1 Does WG14 want to make it a constraint violation if the limit is exceeded?

If not we would introduce a new UB into the preprocessor.

5.2.1.2 Does WG14 want to introduce a hardlimit on the number of expansions as above?

If not, we could

5.3 Different forms of integer literals

Existing practice is that __COUNTER__ expands as a simple base-10 integer literal with no digit separators or suffixes. This could lead to surprising behavior where different expansions of __COUNTER__ have different types, such as 32767 (signed) and 32768 (long) on a system with an INT_WIDTH of 16. There are three options to address this:

We do not propose constraining whether an implementation can expand __COUNTER__ with a suffix. On small freestanding systems it could well make sense to add a L or U suffix, such that a wider spectrum of values without type change is produced.

On the other hand, integer literals now could contain digit separators (’ characters). If so, such a number would not be composable with identifiers by means of the ## operator. Thus we exclude this form of integer literals.

5.3.1 Design Questions

5.3.1.1 Does WG14 want to constrain the form of integer literals to decimal?

If yes, that would deviate significantly from the analog definition for __LINE__.

5.3.1.2 Does WG14 want to constrain the form of integer literals concerning a possible suffix?

If yes, that would again deviate from the analog definition for __LINE__ where no restriction is made.

Note to the editors: When we extended the term integer-literal for C23, it seems that we forgot to disallow digit separators for __LINE__.

5.4 __COUNTER__ and the C library

Many implementations already have the macro as an extension. Since this is not otherwise regulated, implementations of the C library might use it to generate unique identifiers, for example for local variables in type-generic atomic macros or for parameter names of function interfaces. Therefore we think that the C library should not be constrained in using the __COUNTER__ macro and we propose to make this explicit in the text.

6 Suggested Wording

New text is underlined green, removed text is stroke-out red.

6.1 Clause 6.10.5.2, argument substitution

Modify p4

Semantics

4 After the arguments for the invocation of a function-like macro have been identified, argument substitution takes place. A va-opt-replacement is treated as if it were a parameter. For each parameter that occurs in the replacement list that is such that it is neither preceded by a # or ## preprocessing token nor followed by a ## preprocessing token, the preprocessing tokens naming the parameter are replaced by a token sequence determined as follows:

— If the parameter is of the form va-opt-replacement, the replacement preprocessing tokens are the preprocessing token sequence for the corresponding argument, as specified later in this subclause.

— Otherwise, the replacement preprocessing tokens are the preprocessing tokens of the corresponding argument after all macros contained therein have been expanded. The argument’s preprocessing tokens are completely macro replaced before being substituted as if they formed the rest of the preprocessing file with no other preprocessing tokens being available. For each such parameter this expansion is performed exactly once, and then preprocessing tokens naming the parameter are each replaced with the resulting token list.

6.2 Clause 6.10.10.1, general (in Predefined macro names)

The values of the predefined macros listed in the following subclauses216) (except for __COUNTER__, __FILE__ and __LINE__) remain constant throughout the translation unit.

6.3 Clause 6.10.10.2, mandatory names

add an item to the list

__COUNTER__ a per-expansion unique integer literal as described in 6.10.10.2.1.

6.4 new clause 6.10.10.2.1

6.10.10.2.1 The __COUNTER__ predefined macro
Description
1 The expansion of the object-like macro __COUNTER__ produces a unique integer literal not containing any digit separators that relates to the specific lexical position within the pp-token sequence during translation phase 4. The produced integer literal is such that a concatenation with a preceding identifier token using the ## operator again results in an identifier token, and such that when interpreted in translation phase 7 it has a value that is representable with the signed long type. The value of the first expansion in a given translation unit is zero, and each subsequent expansion produces a value that is one greater than the previous one.
2 It is unspecified if the inclusion of any of the standard headers of clause 7 or if the invocations of any macro that is defined by them will result in expansions of the __COUNTER__ macro.
Constraints
3 The number of expansions of the __COUNTER__ macro in a translation unit shall not exceed 2147483647.
Recommended practice
4 It is recommended that implementations either
Application code should not make other assumptions than given in the description about the form, type or value of the integer literal that is produced.
5 EXAMPLE The __COUNTER__ predefined macro is useful for producing unique identifiers:
  #define CONCAT_IMPL(A, B) A ## B
  #define CONCAT(A, B) CONCAT_IMPL(A, B)
  #define UNIQUE_ID(NAME) CONCAT(NAME, __COUNTER__)

  #define SWAP_IMPL(A, B, PTRA, PTRB, TMP) \
     do {                      \
          auto PTRA = &(A);    \
          auto PTRB = &(B);    \
          auto TMP  = *PTRA;   \
          *PTRA     = *PTRB;   \
          *PTRB     = TMP;     \
     } while (false)

  /* A and B are modifiable and addressable lvalues. */
  #define SWAP(A, B)           \
    SWAP_IMPL(A, B,            \
        UNIQUE_ID(ptr),        \
        UNIQUE_ID(ptr),        \
        UNIQUE_ID(val))
If __COUNTER__ has not been expanded earlier in the translation unit, invoking SWAP(X[35], y) expands to
     do {
          auto ptr0 = &(X[35]);
          auto ptr1 = &(y);
          auto val2 = *ptr0;
          *ptr0     = *ptr1;
          *ptr1     = val2;
     } while (false)
or similar. Here, the produced identifiers not only depend on the number of previous expansions of __COUNTER__ but also on the specific form of integer literal that the implementation provides.

6.4.1 Clause 7.1.3, Reserved identifiers

add a new bullet point at the end

— It is unspecified if the inclusion of any of the standard headers or if the invocation of any macro defined in these headers invokes the predefined macro __COUNTER__ even multiple times.

Acknowledgments

Thanks to … for review and discussions.