| Aug | SEP | Oct |
| 19 | ||
| 2024 | 2025 | 2026 |
__COUNTER__ predefined macro2025-01-25
integration into IS ISO/IEC 9899:202y
| document number | date | comment |
|---|---|---|
| n3457 | 202501 | Original proposal |
CC BY, see https://creativecommons.org/licenses/by/4.0
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.
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.
__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__
#endifMeanwhile 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.
A brief survey of some uses of __COUNTER__ in C and C++:
C:
__COUNTER__ for, among
other things:
__COUNTER__ for
unique identifiers in multiple places link
1 link
2__COUNTER__ for unique
identifiers__COUNTER__ auto-incrementing endpoint
numbers; link__COUNTER__ for unique indices in timing
utilities; link__COUNTER__ for unique identifiers througout
the codebase; link__COUNTER__ for lookup
tables as part of a localization and compile-time string hashing
system.Many additional uses include use for static assertions, however, that use case is now covered by C11 and C23 static assertion facilities.
C++:
__COUNTER__
for unique
identifiers, falling back to __LINE__ if __COUNTER__ isn’t present or doesn’t behave
as expected__COUNTER__ for
unique
identifiers__COUNTER__ for unique
identifiers as well as in sanitizer code to prevent
ICF__COUNTER__ for unique
identifiers, falling back to __LINE____COUNTER__
extensively, primarily for unique
identifiers__COUNTER__ for
unique identifier generation, e.g. in crash
logging code, as well as for creating
unique
tags for ABORT()s__COUNTER__ for unique
identifiers, falling back to __LINE__ if not present__COUNTER__ for unique
identifiersAll 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
Our suggested wording below takes several points that could present difficulties into account.
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:
Similarly, if the parameter is never used no expansion should occur:
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:
Notably, Clang produces the desired output in the following example:
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__ // 2We 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.
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.
If not we would introduce a new UB into the preprocessor.
If not, we could
LONG_MAX, but then we should
probably also change the limit for #line to that
value<limits.h>
that holds the limit.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:
__COUNTER__ if their use is sensitive to the
typeWe 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.
If yes, that would deviate significantly from the analog definition
for __LINE__.
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__.
__COUNTER__ and the C libraryMany 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.
New text is , removed text is
stroke-out red.
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 in the replacement list
that isneither 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.
The values of the predefined macros listed in the following subclauses216) (except for
__FILE__and__LINE__) remain constant throughout the translation unit.
add an item to the list
add a new bullet point at the end
Thanks to … for review and discussions.