C++ quirks: variable shadowing

Revision en1, by Xellos, 2026-04-19 01:32:34

C++ is a very permissive language. It'll let you write basically anything on the assumption that you know what you're doing... or let you shoot yourself in the foot if you don't. For example:

class C {
    int a;

  public:
    C(int a) : a{a} {}
};

It's a fairly common pattern in simple class design that's perfectly fine until it's not. The class member and constructor argument have the same name. This is known as shadowing and compilers will warn you about it, though you need to use a flag -Wshadow that isn't included among -Wall or -Wextra. Another example of shadowing is

for(int i = 0; i < t; i++)
    for(int i = 0; i < n; i++)

which is fine if the first i isn't used for anything except making a loop (such as over test cases) but otherwise it's a solid source of bugs, especially if you "fix" it into for(int j = 0; j < n; i++).

How does shadowing work?

First off, when can one variable shadow another? The standard has a lot of horrifying lawyerish explaining it, but in short, two variables with different scopes can have the same name. Scope is where that variable can be used in code. In the examples above:

  • the scope of a class member is the whole class
  • the scope of a function argument is that function
  • the scope of a variable declared inside for() is that for-loop

The next key question is how the compiler decides which variable is used if there are multiple options with the same name. Simple: the one with tighter scope is used. It's called the shadow variable while any variables with larger scopes are called shadowed by it.

That's why using a in the constructor always refers to the constructor argument, while trying to modify the outer loop's i fails if it's shadowed by i in the inner loop. Note that in the member initializer list a{a}, the second a is the shadowing constructor argument, but the first a is unambiguous — it says the class member a should be initialized using something.

Sometimes, it's possible to refer to a variable more clearly, not just by its name. A typical example is its namespace, that's why using namespace is bad practice as it removes the ability to disambiguate (it's fine in small self-contained sources that don't use other namespaces) or class name for static variables. With regular class members, there's something called "implicit this", where a can be understood to mean this->a i.e. the member, unless there's something like a shadow variable. Shadowing is one danger of relying on implicit this. If the constructor of C was something complex, a programmer that likes pain can use a for the argument and this->a for the member, so there's no ambiguity (but it's still shadowing).

Of course, the best practice is different names for different variables with overlapping scopes. The Google C++ Style Guide recommends naming member variables with a trailing underscore; I prefer naming them normally and using trailing underscore to disambiguate constructor arguments like in the example up top when there's no clearer name to give. When there's no intentional shadowing, the -Wshadow flag is useful for preventing bugs.

Fixing testlib

Compiling a testlib-using program locally reveals quite a lot of warnings about shadow variables. Those aren't actual errors but it's still inconvenient. Let's deal with them!

Tags c++, warning, testlib, code review

History

 
 
 
 
Revisions
 
 
  Rev. Lang. By When Δ Comment
en3 English Xellos 2026-04-20 17:25:58 544 how to suppress warnings
en2 English Xellos 2026-04-20 16:57:44 4564 (published)
en1 English Xellos 2026-04-19 01:32:34 3614 Initial revision (saved to drafts)