Firstly, the rules in current working draft are laid out in [lex.name] p3:
In addition, some identifiers appearing as a token or preprocessing-token are reserved for use by C++ implementations and shall not be used otherwise; no diagnostic is required.
- Each identifier that contains a double underscore
__
or begins with an underscore followed by an uppercase letter is reserved to the implementation for any use.
- Each identifier that begins with an underscore is reserved to the implementation for use as a name in the global namespace.
Furthermore, the standard library reserves all names defined in namespace std
and some zombie names; see [reserved.names.general].
What about POSIX?
As the accepted answer has pointed out, there may be other parts of the implementation, like the POSIX standard, which limit the identifiers you can use.
Each identifier with file scope described in the header section is reserved for use as an identifier with file scope in the same name space if the header is included.
ANY Header [reserves] Suffix _t
- POSIX 2008 Standard, 2.2.2
In C++, almost all problems associated with POSIX can be avoided through namespaces.
This is also why the C++ standard can add tons of symbols like std::enable_if_t
without breaking POSIX compatibility.
Visualization
int x; // OK
int x_; // OK
int _x; // RESERVED
int x__; // RESERVED (OK in C)
int __x; // RESERVED
int _X; // RESERVED
int assert; // RESERVED (macro name)
int x_t; // RESERVED (only by POSIX)
namespace {
int y; // OK
int y_; // OK
int _y; // OK
int y__; // RESERVED (OK in C, ignoring namespaces)
int __y; // RESERVED
int _Y; // RESERVED
int assert; // RESERVED (macro name)
int y_t; // OK
}
The above rules for y
apply to both named and unnamed namespaces.
Either way, in the following namespace, the rules of the global namespace
no longer apply (see [namespace.unnamed]).
The above rules for y
also apply to identifiers in classes, functions, etc.; anything but global scope.
Even though assert
isn't used like a function-style macro here, the name is reserved. This is also why proposal P2884 contemplates making it a keyword in C++26, with some success so far.
Recommended Practice
To be safe, always avoid double underscores, and always avoid nams with leading underscores.
The latter are okay in some cases, but it's difficult to memorize these rules, and it's better to be safe than sorry.
What about _
in itself?
Some people use _
to indicate that some variable or function parameter isn't used. However, you can avoid this with:
void foo(T _) { /* ... */ }
// replace with:
void foo(T) { /* ... */ }
std::scoped_lock _{mutex};
// replace with:
std::scoped_lock lock{mutex};
You can also cast a parameter p
to void
like (void)p
, if this is about silencing warnings about p
being unused, and you need C compatibility. See Why cast unused return values to void?.