1. Introduction
The
pre-defined macro is a language extension for C and C++ which expands to an integer literal that
starts at
and increments by
every time it expanded in a translation unit. This is a widely-used utility that
is primarily useful for generating unique identifiers with the preprocessor.
2. Rationale for Standardization
is de-facto portable today. Nearly every implementation supports it with unsurprising semantics. However,
its lack of standardization results in uncertainty about its portability and semantics. For example, libraries 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 libraries and C++ developers avoid it altogether due to this uncertainty. While every compiler today
supports
it’s not always enabled. For example, EDG only provides it in microsoft mode. In the absence of
cautious checking and fallback, a developer must consult numerous widely used C++ implementations to convince themselves
that
exists and does what they want.
Due to its widespread nature, it would be useful to define the semantics of
as part of the official
standard. Standardizing
makes existing use more clearly defined and portable while also providing a useful
utility as a standard language feature.
3. Motivating Examples
A brief survey of some uses of
in the C and C++ community:
-
Google benchmark uses
for unique identifiers, falling back to__COUNTER__
if__LINE__
isn’t present or doesn’t behave as expected__COUNTER__ -
Google Orbit uses
for unique identifiers__COUNTER__ -
LLVM uses
for unique identifiers as well as in sanitizer code to prevent ICF__COUNTER__ -
Catch2 uses
for unique identifiers, falling back to__COUNTER__ __LINE__ -
Tensorflow uses
extensively, primarily for unique identifiers__COUNTER__ -
Chromium uses
for unique identifier generation, e.g. in crash logging code, as well as for creating unique tags for__COUNTER__
sABORT () -
Folly uses
for unique identifiers, falling back to__COUNTER__
if not present__LINE__ -
v8 uses
for unique identifiers__COUNTER__ -
Metric Panda Games uses
for lookup tables as part of a localization and compile-time string hashing system.__COUNTER__
4. Implementation Support
has long been supported by all major implementations of C and C++:
Compiler | Earliest Version Tested | Earliest Version Tested Supporting
|
---|---|---|
GCC | 3.4.6 ❌ | 4.4.7 ✔️ |
Clang | 3.0.0 ✔️ | 3.0.0 ✔️ |
MSVC | 19.0 ✔️ | 19.0 ✔️ |
ICC | 13.0.1 ✔️ | 13.0.1 ✔️ |
ICX | 2021.1.2 ✔️ | 2021.1.2 ✔️ |
EDG | 6.5 🟡 | 6.5 🟡 |
TCC | 0.9.27 ✔️ | 0.9.27 ✔️ |
Movfuscator | Trunk ✔️ | Trunk ✔️ |
🟡: Supported only in
mode
Comparison: https://godbolt.org/z/Pf1vr8T9e
5. Design Considerations
5.1. Precompiled Headers
MSVC and GCC save the state of
in precompiled headers. GCC notes that the
macro must not be
expanded prior to inclusion of a pre-compiled header. If it is, then the precompiled header is not used.
This paper proposes no change to the current behavior.
5.2. Modules
GCC and MSVC do not propagate
across modules, including for header units. The following compiles with a
linker error due to multiple definitions of
:
// header.hpp #define CONCAT_IMPL(x, y) x##y #define CONCAT(x, y) CONCAT_IMPL(x, y) #define NEW_VAR(name) CONCAT(name, __COUNTER__) int NEW_VAR ( x ); // x0 int NEW_VAR ( x ); // x1 // main.cpp import "header.hpp" int NEW_VAR ( x ); // x0
There are similar concerns with
and
macros surrounding header units, though the potential for
problems is less pronounced. One option would to disallow the expansion of
in header units, however, no
such restriction is proposed in this paper.
This paper proposes no change to the current behavior. Other behaviors would introduce additional complexity without clear benefit.
5.3. ODR
It’s possible to inadvertently violate ODR with
:
// foo.hpp #define CONCAT_IMPL(x, y) x##y #define CONCAT(x, y) CONCAT_IMPL(x, y) #define NEW_VAR(name) CONCAT(name, __COUNTER__) inline void foo () { int NEW_VAR ( x ) = 2 ; } // a.cpp #include "foo.hpp"// b.cpp int x = __COUNTER__ ; #include "foo.hpp"
Current implementations do not make any special attempt to diagnose or prevent such use of
beyond existing
ODR diagnostics. Similar ODR issues can occur as a result of
and
. While existing practice is that
these ODR issues exist, it is worthwhile looking at possible solutions to the problem.
5.3.1. Possible Solutions
This is a difficult problem to solve due to the nature of
and how the preprocessor interacts with the rest
of the language. Possible solutions include:
-
Just don’t use
in__COUNTER__
functions in headersinline -
Provide a mechanism to reset the
, or even push and pop the counter__COUNTER__ -
Allow for multiple counters
, possibly tied to__COUNTER__ __FILE__ -
Change ODR to allow
andinline void foo () { int x0 ; }
to not be ill-formedinline void foo () { int x1 ; } -
Some sort of deterministic
or__UUID__
macro that is tied to the file and line__UNIQUE__
Most of these would not be practical, would add boilerplate, or would introduce substantial complexity.
5.3.2. Proposed Solution
This paper proposes no fundamental changes to existing
functionality or language semantics. Instead,
unique identifiers for variables in header-
functions should be solved by:
-
Modules, where
is module-local__COUNTER__ -
The
placeholder [P2169], which is ODR-friendly_
This proposal does not preclude additional functionality or other approaches to make
more ODR-friendly at
a later time.
5.3.2.1. Is __COUNTER__
still needed?
is largely sufficient for uses of
in the case of local identifiers, however, it does not cover
use-cases of
in namespace-scoped identifiers.
As an example of use of
outside local functions, google benchmark uses uniquely-named identifiers at
namespace-scope to register benchmark functions:
// after preprocessor expansion: static :: benchmark :: internal :: Benchmark * _benchmark_2FooBar __attribute__ (( unused )) = ( :: benchmark :: internal :: RegisterBenchmarkInternal ( new :: benchmark :: internal :: FunctionBenchmark ( "FooBar" , FooBar ) ) );
An alternative to
in cases such as this would be to standardize
to manage any objects, it is a pure constructor, however, in cases where
an object is managed and possibly needs to be destructed at the end of a program the approach of a namespace-scoped
variable has the benefit of consolidating constructor and destructor logic around an object, as opposed to managing an
object between free functions and a variable, e.g.:
std :: optional < Foo > obj ; __attribute__ (( constructor )) void obj_setup () { obj = setup_foo (); } /* possibly a destructor too */ // vs Foo obj = setup_foo (); /* or some raii-wrapper around Foo if additional destruction logic is needed beyond normal */
While
does cover much of the use of
, it is still useful for standardization due to existing widespread
use, unique identifiers outside inline functions, and other uses of
beyond unique identifiers.
5.4. Range and Overflow
is implemented with an
counter in GCC and Clang and both implementations wrap around to zero
when that counter overflows. This paper recommends
shall be able to attain a value of at
least 232 - 1 with an error on overflow.
6. Proposed Wording
Proposed wording relative to [N4950]:
Insert a bullet point in [cpp.predefined/1] before bullet 3:
__COUNTER__
A decimal literal consisting only of digits representing the value of a preprocessor-internal counter. The value of the counter starts atand is incremented by
0 each time the
1 macro is expanded. The counter shall have a maximum value of at least 232 - 1. If the value of the counter exceeds its implementation-defined maximum value the program is ill-formed.
__COUNTER__
Insert a row into Table 22 in [cpp.predefined/1] after the row for
:
Macro name | Value |
---|---|
|
|
Update [cpp.predefined/3]:
The values of the predefined macros (except for
__FILE__ and,, and
__LINE__ ) remain constant throughout the translation unit.
__COUNTER__