PnnnnR0
__COUNTER__

New Proposal,

This version:
https://jeremy-rifkin.github.io/cpp-proposals/drafts/counter_draft_2.html
Author:
Jeremy Rifkin
Project:
ISO/IEC 14882 Programming Languages — C++, ISO/IEC JTC1/SC22/WG21
Source:
https://github.com/jeremy-rifkin/cpp-proposals/blob/main/src/counter.bs

Abstract

__COUNTER__ is a widely-used predefined macro provided as a language extension by all major C and C++ implementations. This paper aims to standardize existing practices.

1. Introduction

The __COUNTER__ pre-defined macro is a language extension for C and C++ which expands to an integer literal that starts at 0 and increments by 1 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

__COUNTER__ 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 __COUNTER__ 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 __COUNTER__ exists and does what they want.

Due to its widespread nature, it would be useful to define the semantics of __COUNTER__ as part of the official standard. Standardizing __COUNTER__ 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 __COUNTER__ in the C and C++ community:

4. Implementation Support

__COUNTER__ has long been supported by all major implementations of C and C++:

Compiler Earliest Version Tested Earliest Version Tested
Supporting __COUNTER__
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 --microsoft mode

Comparison: https://godbolt.org/z/Pf1vr8T9e

5. Design Considerations

5.1. Precompiled Headers

MSVC and GCC save the state of __COUNTER__ in precompiled headers. GCC notes that the __COUNTER__ 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 __COUNTER__ across modules, including for header units. The following compiles with a linker error due to multiple definitions of x0:

// 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 __TIME__ and __DATE__ macros surrounding header units, though the potential for problems is less pronounced. One option would to disallow the expansion of __COUNTER__ 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 __COUNTER__:

// 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 __COUNTER__ beyond existing ODR diagnostics. Similar ODR issues can occur as a result of __DATE__ and __TIME__. 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 __COUNTER__ and how the preprocessor interacts with the rest of the language. Possible solutions include:

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 __COUNTER__ functionality or language semantics. Instead, unique identifiers for variables in header-inline functions should be solved by:

  1. Modules, where __COUNTER__ is module-local

  2. The _ placeholder [P2169], which is ODR-friendly

This proposal does not preclude additional functionality or other approaches to make __COUNTER__ more ODR-friendly at a later time.

5.3.2.1. Is __COUNTER__ still needed?

_ is largely sufficient for uses of __COUNTER__ in the case of local identifiers, however, it does not cover use-cases of __COUNTER__ in namespace-scoped identifiers.

As an example of use of __COUNTER__ 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 __COUNTER__ in cases such as this would be to standardize __attribute__((constructor)). Google benchmark does not rely on _benchmark_2FooBar 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 __COUNTER__, it is still useful for standardization due to existing widespread use, unique identifiers outside inline functions, and other uses of __COUNTER__ beyond unique identifiers.

5.4. Range and Overflow

__COUNTER__ is implemented with an unsigned counter in GCC and Clang and both implementations wrap around to zero when that counter overflows. This paper recommends __COUNTER__ 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:

Insert a row into Table 22 in [cpp.predefined/1] after the row for __cpp_constinit:

Macro name Value
__cpp_counter 20XXXXL

Update [cpp.predefined/3]:

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

References

Normative References

[N4950]
Thomas Köppe. Working Draft, Standard for Programming Language C++. 10 May 2023. URL: https://wg21.link/n4950

Informative References

[P2169]
A nice placeholder with no name. URL: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2169r4.pdf