5

When I add -Wshadow flag, compiler (gcc 12.1.0) generates a following warning for the below code:

prog.cc: In member function 'void Derived::myfunc()':
prog.cc:16:13: warning: declaration of 'i' shadows a member of 'Derived' [-Wshadow]
   16 |         int i = 3;
      |             ^
prog.cc:8:9: note: shadowed declaration is here
    8 |     int i{2};
      |       
class Base
{
private:
    int i{2};
};

class Derived : public Base
{
public:
    void myfunc(void)
    {
        int i = 3;
        std::cout << i << "\n";
    }
};

What I am thinking about it is: because we cannot use just "i" variable inside of myfunc (because it is compile time error), it is unnecessary to raise such a warning.

Do you have any idea about why compiler gives a warning in this case ?

user3104363
  • 182
  • 1
  • 14
  • 1
    It generates a compile-time error because the variable `i` is declared `private` in the base class. But if it wasn't, since you use `public` inheritance, `i` would be accessible from the derived class too, hence the shadowing warning. The warning here seems to be more generic than your specific use-case. – Fareanor Jan 11 '23 at 14:35
  • 7
    `Base::i` is still there. You cannot access it, but it is there and affects name lookup – 463035818_is_not_an_ai Jan 11 '23 at 14:36
  • https://en.cppreference.com/w/cpp/language/lookup – 463035818_is_not_an_ai Jan 11 '23 at 14:37
  • 2
    Are you actually trying to ask why this **is** shadowing, or why the compiler warns about this shadowing in particular since you don't think it should cause a practical problem? – Useless Jan 11 '23 at 14:39
  • also remember that `private` does not mean that there is absolutely no means to access it. The fact that it is `private` means that it is not easily accessible and that you should not jump through hoops to access it, but there are hoops – 463035818_is_not_an_ai Jan 11 '23 at 14:41
  • It's necessary to generate such a warning because you told it to do so and it could cause a bug having two variables with the same name. – drescherjm Jan 11 '23 at 14:44
  • 2
    Basically because it's impossible to know whether you intended to declare a local variable or to assign to `i`. The assignment would not compile due to its being private, but it's also impossible to know whether you really intended it to be private. – molbdnilo Jan 11 '23 at 14:46
  • I am not sure if this is just an example or representative of actual code either way in my code I would never have an `i` variable as a class member. I would only use `i` as a loop index and nothing more. – drescherjm Jan 11 '23 at 14:50
  • fwiw here is a hack that makes use of the fact that lookup happens before access restrictions apply https://stackoverflow.com/q/32566579/4117728 (but beware, don't do this at home, erm... at work) – 463035818_is_not_an_ai Jan 11 '23 at 15:20
  • What are you trying to do anyway? – Pepijn Kramer Jun 18 '23 at 05:56

2 Answers2

9

Shadowing is about name lookup, not access rules

It's by design, particularly not taking into account access rules but rather shadowing via how it affects lookup results. From GCC's description:

-Wshadow

Warn whenever a local variable or type declaration shadows another variable, parameter, type, class member (in C++), or instance variable (in Objective-C) or whenever a built-in function is shadowed. Note that in C++, the compiler warns if a local variable shadows an explicit typedef, but not if it shadows a struct/class/enum. If this warning is enabled, it includes also all instances of local shadowing. This means that -Wno-shadow=local and -Wno-shadow=compatible-local are ignored when -Wshadow is used. Same as -Wshadow=global.

That the shadowed i is a class member via inheritance and moreover private is orthogonal to the fact that it is being shadowed in the sense of what name lookup will find.

Example

As an example as to why this could be problematic, consider the following delta, which introduces a bug in the a contrived program.

Program at time N:

struct A { 
    // Use 0 to indicate that "all is good"
    static constexpr int kStatusIsGood = 0;
    int status{}; // zero-initializer to 0, oops
};

struct B : A {
    constexpr bool clientIsGood(int status) {
        // warning: declaration of 'status' shadows a member of 'B' [-Wshadow]

        (void)status; // logging, using it somehow

        // Key result of the function
        return status == kStatusIsGood;
    }
};

constexpr int kClientPanic = 42;
static_assert(!B{}.clientIsGood(kClientPanic));

Program at time N+1

Where a dev has changed the name of the status parameter but forgot to update the function body, which silently passes compilation due to a shadowed variable now being found by lookup:

struct A { 
    // Use 0 to indicate that "all is good"
    static constexpr int kStatusIsGood = 0;
    int status{}; // zero-initializer to 0, oops
};

struct B : A {
    constexpr bool clientIsGood(int client_status) {       
        (void)client_status; // logging, using it somehow

        // Ups, a bug was left here
        return status == kStatusIsGood;
    }
};

constexpr int kClientPanic = 42;
static_assert(B{}.clientIsGood(kClientPanic));  // "all is good :)"
dfrib
  • 70,367
  • 12
  • 127
  • 192
0

An i variable from the base class is in the existing derived class. A variable with the same name is defined again in the function. The name search is terminated as soon as the function is in scope. That's why the variable in the base class is not used and is stated to shadow it.

embeddedstack
  • 362
  • 1
  • 13