2

This code compiles with no warnings in gcc-11:

    int i{ 2 };
    {
        std::cout << i;   //prints 2
        int i{ 3 };
        std::cout << i;   //prints 3
    }

Is this well defined or it just happened to work?

really
  • 93
  • 7

2 Answers2

3

Yes, it is. The scope of the second i, and therefore the area in which it shadows the first i, is from its declarator(1) down to the end of the enclosing block. It's the same reason why this is invalid:

{
    std::cout << i;
    int i{ 3 };
}

But, of course, this surely won't happen with any of your code because, as a professional developer, you know better than to use one-character variable names(2) for anything other than very short loops, or to reuse variable names in a confusing manner :-)


(1) The standard (C++20) discusses the concepts of "points of declaration", "potential scope", and "scope", in [basic.scope], and this is the definitive place to look for the rules.

Variable names exist between their point of declaration and the end of their block (their potential scope), excluding areas where they are shadowed (shadowing is the difference potential and actual scope).

The point of declaration for int i{3} is effectively between the i and the { (after declaration but before initialisation).

This can lead to subtle issues such as:

int i = 7;
{
    int i {i};
    // Value of i here is indeterminate.
}

and is usually reason enough to avoid shadowing.


(2) You're not paying cold hard cash for each character in your variable names so it's best not to be stingy :-)

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • Thanks! Sure, this is a short version of something I found while searching for a bug – really Mar 10 '22 at 21:33
  • 1
    `int i {i};` by itself is already always undefined behavior, because `i` is accessed before its lifetime begins. – user17732522 Mar 10 '22 at 22:05
  • @user17732522, I'm not sure that's quite correct, the scope begins before the opening brace so it's *lifetime* has begun before access inside the braces. – paxdiablo Mar 10 '22 at 23:22
  • 2
    @paxdiablo No, this had been clarified in C++20 (but it was also UB before that for `int` type due to read of an indeterminate value). See the example in https://timsong-cpp.github.io/cppwp/n4868/basic#scope.pdecl-example-1. The point of declaration is immediately after the declarator before `{`, but the point at which lifetime starts is immediately after the initialization is completed. (https://timsong-cpp.github.io/cppwp/n4868/basic#life-1.2). – user17732522 Mar 10 '22 at 23:28
  • Awesome, @user17732522, it looks like its scope starts earlier than its lifetime :-) However, that link seems to be for the first C++23 draft, C++20 states that's the case only for non-vacuous. I assume your C++20 is just a typo. Still, that makes no difference, it's not a construct anyone should be using, whether it's one type of UB or another :-) – paxdiablo Mar 11 '22 at 10:01
  • @user17732522 Is there a plan to *fix* this behavior in future C++ standards? In Lua, `local i = i` declares a new local variable `i`, initializing it the outer scope `i` variable (possibly a global one). So in Lua, the scope of a local variable begins at the **next** instruction after `local` declaration. And this is quite handy, notably when dealing with modules. Why C++ could not do the same? As `int i{i};` is currently undefined behavior so useless, I don't think it would break any existing code. Maybe just break compilation of some nonsensical code. – prapin Mar 11 '22 at 19:37
  • 1
    @paxdiablo The last draft before C++20 has the same wording. The change happened between C++17 and C++20. Maybe you looked at the wrong draft. But anyway, as you said, doesn't really matter which kind of UB it is. – user17732522 Mar 11 '22 at 20:25
  • @prapin I am not aware of any such proposals. The behavior of the scoping rule is inherited from C and I doubt such a backwards compatibility break would be acceptable. However (I think) in C the lifetime rules are less strict, so that you could store a value in the variable before it is initialized. In C++ it is still allowed to form a pointer to the variable in its own initializer. However, I think the most common use is to avoid having to repeat the type in unevaluated contexts, e.g. in C: `int* x = malloc(sizeof *x);`. – user17732522 Mar 11 '22 at 20:25
2

Is this well defined or it just happened to work?

It is well-defined. The scope of a variable declared inside a {...} block starts at the point of declaration and ends at the closing brace. From this C++17 Draft Standard:

6.3.3 Block scope      [basic.scope.block]

1     A name declared in a block (9.3) is local to that block; it has block scope. Its potential scope begins at its point of declaration (6.3.2) and ends at the end of its block. A variable declared at block scope is a local variable.


This code compiles with no warnings in gcc-11

That surprises me. The clang-cl compiler (in Visual Studio 2019, 'borrowing' the /Wall switch from MSVC) gives this:

warning : declaration shadows a local variable [-Wshadow]

Using both -Wall and -Wpedantic in GCC 11.2 doesn't generate this warning; however, explicitly adding -Wshadow does give it. Not sure what "general" -Wxxx switch GCC needs to make it appear.

Adrian Mole
  • 49,934
  • 160
  • 51
  • 83