Underscores in C++: Silent Killers of Your Code
\n\nImagine this: you're writing clean, beautiful C++ code. The compiler is silent, the linter doesn't complain, everything works perfectly. And then — bam! — on some other compiler or with a new version of the standard, everything breaks for no apparent reason. Sound familiar?
\n\nMost often, the root of the evil lies in seemingly harmless underscore characters (_). This topic is a real "graveyard" for beginners and even experienced C++ developers. It's no wonder that a Stack Overflow question about the rules for using underscores has garnered tens of thousands of views. Let's finally figure out what is allowed, what is not, and how to avoid the trap of undefined behavior (UB).
Why is the C++ Standard So Strict About Underscores?
\n\nThe root of the problem lies in compatibility and the global namespace. The C++ standard library actively uses underscores for its internal needs. If you accidentally create an identifier that conflicts with the library's or compiler's internal names — that's it, you're done for. The compiler might silently redefine your variable, and you'll get Undefined Behavior, which will manifest itself at the most inopportune moment.
\n\nThe C++ standard (starting from C++03 and in all subsequent versions, including C++20 and C++23) clearly specifies which identifiers are reserved for the implementation. Violating these rules makes your code not just incorrect, but non-portable.
\n\nTop 5 Deadly Mistakes with Underscores
\n\nLet's break down the most common pitfalls that even seasoned programmers stumble upon.
\n\nMistake #1: Double Underscore Anywhere
\n\nRule: Any identifier containing two consecutive underscore characters (__) anywhere is reserved for the implementation.
// BAD! Reserved for the "implementation"\nint __myVariable = 42;\nvoid my__function();\nclass My__Class {};\n\n// GOOD\nint myVariable = 42;\nvoid my_function();\nclass MyClass {};\n\n\nWhy is this dangerous? The compiler might use __myVariable for its internal macros. For example, many compilers define __LINE__ or __cplusplus. If you create your own __LINE__, the compiler might simply ignore your definition or, worse, replace it with its own value. The result is UB.
Mistake #2: Underscore + Uppercase Letter in Global Scope
\n\nRule: An identifier starting with an underscore followed by an uppercase letter (A-Z) is reserved for the implementation in any scope.
\n\n// BAD! Even in local scope!\nvoid foo() {\n int _MyVar = 10; // Rule violation\n}\n\n// BAD! Globally — even more so\nint _GlobalVar = 20;\n\n// GOOD\nvoid foo() {\n int myVar = 10;\n}\n\n\nThis rule is the most insidious. Many think: "Well, I'm inside a function, who does this bother?" It does! The standard makes no exceptions for local scope if the identifier starts with an underscore and an uppercase letter. The compiler has every right to reserve such names for its own purposes.
\n\nMistake #3: Underscore at the Beginning + Lowercase Letter in Global Scope
\n\nRule: An identifier starting with an underscore followed by a lowercase letter (a-z) is reserved for the implementation in global scope (namespace scope).
\n\n// BAD! In the global namespace\nint _myGlobal = 0;\nvoid _helperFunction();\n\n// GOOD — inside a function or class\nclass MyClass {\n int _memberVar; // Inside a class — allowed (but m_ is better)\n};\n\nvoid foo() {\n int _localVar = 5; // Inside a function