Consider this example
#include <iostream>
int main(){
switch(int a = 1){ //#condition
case 1: switch(int a = 2){}
case 2: switch(int a = 2){}
}
}
why the redeclarations of a are well-formed in this example?
According to the following rule:
basic.scope.block#3
Names declared in the init-statement, the for-range-declaration, and in the condition of if, while, for, and switch statements are local to the if, while, for, or switch statement (including the controlled statement), and shall not be redeclared in a subsequent condition of that statement nor in the outermost block (or, for the if statement, any of the outermost blocks) of the controlled statement.
IIUC, both the declarations in statement switch(int a = 2){} or switch(int a = 2){} are all in the outermost block of the controlled statement which is a compound-statement.
As contrast:
#include <iostream>
int main(){
switch(int a = 1){ //#condition
case 1: int a = 2;
}
}
The redeclaration of a after case 1 is ill-formed since it's redeclared in the outermost block of that statement.
Clarify
According to stmt.block, A block is an alias of a compound-statement. So the above rule totally says about block, it's regardless of scope. The rule is equivalent to:
shall not be redeclared in the outermost compound-statement of the controlled statement.
So, what I can't understand here is that, since there's no any block between the condition of inner switch and the outermost block of the first switch, how could say that the condition of the inner switch is not in the outermost block of the outer switch?
switch(int a = 1){ <- outermost block of the primary `switch`
case 1: switch(int a = 2 /*there's no any block contains this condition*/){}
}
As contrast:
switch(int a = 1){ <- outermost block of the primary `switch`
case 1: { /* here exists a block between `int a = 2` and outermost block of the primary `switch`, so the condition definitely not in the outermost block*/
switch(int a = 2 ){}
}
}
Is there any rule in the standard that I have missed says about the transformation which is similar to stmt.while#2, which will make the condition be contained in an invented block(compound-statement)?
The real question is the meaning of in here. Anything between the { and } that delimit the switch is of course in that “outermost block”, but the use of “outermost” clearly implies that we’re not supposed to consider portions of the program that are (also) inside a nested block. The simplest way to reach that interpretation is to read “in” as “directly in”, in the same sense that “a function declared in a namespace” does not usually include member functions of classes in that namespace. The condition of a nested switch is then exempt because declarations in it are not directly in any block.
P1787R6, which was adopted in November 2020, clarifies the situation by rewriting [basic.scope.block] to specifically refer to the singular scope associated with the substatement, independently of transformations like that in [stmt.while]/2.
There's no such rule. The code in stmt.while/2 is misleadingly redundant; it could equally be written
label:
if ( condition ) {
statement
goto label ;
}
It is necessary to look at the meaning of the word "in" in stmt.pre/5 and basic.scope.block/3:
Names declared in the init-statement, the for-range-declaration, and in the condition of if, while, for, and switch statements are local to the if, while, for, or switch statement (including the controlled statement), and shall not be redeclared in a subsequent condition of that statement nor in the outermost block (or, for the if statement, any of the outermost blocks) of the controlled statement.
Here, "in" means "immediately in"; it's referring to declaration-statements which would have scope the remainder of the block. Declarations via the condition of a selection statement are not "in" the enclosing block, they are "in" that selection statement.
Related
I have found multiple questions regarding the subject of defining variables inside a switch construct, but I have not yet found a clear answer to this question.
Chapter 5.3.2 of the book C++ Primer says the following:
As we’ve seen, execution in a switch can jump across case labels. When execution jumps to a particular case, any code that occurred inside the switch before that label is ignored.
Considering this information, I do not understand why the example below is legal. If control jumps to the false case, it should ignore the true case. This means that, assigning to i should be illegal, because it was never declared. Why is this construct legal?
case true:
int i;
break;
case false:
i = 42;
break;
Declaration is a compile-time thing, and what happens at runtime is irrelevant to that fact. i is visible at any point within the same or child scope after its declaration.
There is nothing that causes a scope change between the two cases, so i remains visible in the false case regardless of whether the true case executed.
This is why you may see anonymous blocks ({ }) used to artificially constrain scope in switch cases. It's to prevent exactly this potential issue (though in this case it's not an issue).
case true: {
int i;
break;
} // Closing this block causes the end of i's lifetime.
case false: {
i = 42; // Compile-time error; i is no longer in scope.
break;
}
Note that your code becomes illegal just by initializing i. Jumps cannot cross over initialization in either direction.
case true:
int i = 0;
break;
case false: // error: jump to case label crosses initialization
i = 42;
break;
Also, any variable of a type that is not trivial cannot have a lifetime spanning cases even if it is not explicitly initialized.
case true:
std::string i;
break;
case false: // error: jump to case label crosses initialization
i = "42";
break;
The fix in this case is to use anonymous blocks to constrain the scope the declaration of i to not span multiple cases.
The relevant standardese:
It is possible to transfer into a block, but not in a way that bypasses declarations with initialization. A program that jumps* from a point where a variable with automatic storage duration is not in scope to a point where it is in scope is ill-formed unless the variable has scalar type, class type with a trivial default constructor and a trivial destructor, a cv-qualified version of one of these types, or an array of one of the preceding types and is declared without an initializer.
-- C++14 (N4296) [stmt.dcl.3]
The footnote (*) regarding jumps:
The transfer from the condition of a switch statement to a case label is considered a jump in this respect.
This question already has answers here:
Error redeclaring a for loop variable within the loop
(3 answers)
Closed 4 years ago.
In the C language, if we write this:
for(int i = 0; i < 7; i++)
{
// for loop Body
}
The scope of variable i is inside the for loop body. It is OK.
But, if I write this:
for(int i = 0; i < 7; i++)
{
long int i = 1; // Redeclaration of i
}
Here, the variable i is declared again inside the loop body, yet it successfully compiles and runs in C.
But, in C++, the compiler gives a "redeclaration of 'long int i'" error.
So, why doesn't the C compiler give a redeclaration error? Is it a compiler bug?
C++ and C make a distinction here. According to C11 (n1570) §6.8.5 ¶5, emphasis mine:
An iteration statement is a block whose scope is a strict subset of
the scope of its enclosing block. The loop body is also a block whose
scope is a strict subset of the scope of the iteration statement.
Which translates to this for a for loop:
{
declaration
while ( expression) {
statement
expression ;
}
}
Anything you put in the statement part can hide whatever is introduced in the declaration. Now, C++(17, n4659) explicitly says something similar at [stmt.for]/1. But it also goes on to add:
except that names declared in the init-statement are in the same
declarative region as those declared in the condition,
So here the second i is indeed an attempt at a re-declaration. The above may sound confusing (names declared in the condidion!?), but the "condition" here is defined like this ([stmt.stmt]/1):
condition:
expression
attribute-specifier-seq decl-specifier-seq declarator brace-or-equal-initializer
It's that which allows while loops like this (C++ only):
while (T t = x) statement
And you may not re-declare t inside the statement of the while loop.
Alternatively, the entire conclusion I reach is summarized in [stmt.iter]/3 (thank you #T.C.):
If a name introduced in an init-statement or for-range-declaration is
redeclared in the outermost block of the substatement, the program is
ill-formed.
First of all, there is no re-declaration inside the loop. Inside the loop body. i is defined every time the loop is entered, and the scope is only till the loop body.
Once the loop finishes the iteration, the variable does not exist anymore. (First quote)
That being said, what you are seeing is a result of shadowing. (Second quote)
Quoting C11,
If the declarator or type specifier that
declares the identifier appears inside a block or within the list of parameter declarations in
a function definition, the identifier has block scope, which terminates at the end of the
associated block. [...]
and,
[...] If an identifier designates two different entities in the same name
space, the scopes might overlap. If so, the scope of one entity (the inner scope) will end
strictly before the scope of the other entity (the outer scope). Within the inner scope, the
identifier designates the entity declared in the inner scope; the entity declared in the outer
scope is hidden (and not visible) within the inner scope.
It's not redeclaration. (Not literally, some may still call it though)
See this carefully...
for(int i = 0; i < 7; i++)
{
printf("i = %d\n", i);
int i = 5;
printf("new i = %d\n", i);
}
Output of the above code:-
i = 0
new i = 5
i = 1
new i = 5
i = 2
new i = 5
i = 3
new i = 5
i = 4
new i = 5
i = 5
new i = 5
i = 6
new i = 5
Clearly, there are two different i's
The newer i has a more local scope.
Is it a bug?
No
What is the purpose?
If it wasn't allowed, it might be very hard to maintain large projects, as you would constantly run into naming collisions.
Generally though, it is considered very poor practice to give the same name to different variables in different scopes, you should avoid doing that whenever possible.
Why is there no warning message?
Use gcc file_name.c -Wshadow to compile.
EDIT: You can also locally lock the originally declared variables by redeclaring them in for loops.
The following syntax is valid:
while (int i = get_data())
{
}
But the following is not:
do
{
} while (int i = get_data());
We can see why via the draft standard N4140 section 6.4:
1 [...]
condition:
expression
attribute-specifier-seqopt decl-specifier-seq declarator = initializer-clause
attribute-specifier-seqopt decl-specifier-seq declarator braced-init-list
2 The rules for conditions apply both to selection-statements and
to the for and while statements (6.5). [...]
and section 6.5
1 Iteration statements specify looping.
iteration-statement:
while ( condition ) statement
do statement while ( expression ) ;
Instead, you're forced to do something ugly like:
int i = get_data();
do
{
} while ((i = get_data())); // double parentheses sic
What is the rationale for this?
It seems like scoping would be the issue, what would be the scope of i declared in the while portion of a do while statement? It would seem rather unnatural to have a variable available within the loop when the declaration is actually below the loop itself. You don't have this issue with the other loops since the declarations comes before the body of the loop.
If we look at the draft C++ standard section [stmt.while]p2 we see that for the while statement that:
while (T t = x) statement
is equivalent to:
label:
{ // start of condition scope
T t = x;
if (t) {
statement
goto label;
}
} // end of condition scope
and:
The variable created in a condition is destroyed and created with each iteration of the loop.
How would we formulate this for the do while case?
and as cdhowie points out if we look at section [stmt.do]p2 it says (emphasis mine):
In the do statement the substatement is executed repeatedly until the
value of the expression becomes false. The test takes place after each
execution of the statement.
which means the body of the loop is evaluated before we would even reach the declaration.
While we could create an exception for this case it would violate our intuitive sense that in general the point of declaration for a name is after we see the complete declaration(with some exceptions for example class member variables) with unclear benefits. Point of declaration is covered in section 3.3.2.
There are several reasons for why it would be difficult to allow.
The language sticks to the general rule that everything should be declared above the point of usage. In this case the variable declared in do-while would be declared below its expected natural scope (the cycle body). Making this variable accessible inside the cycle would've required a special treatment for do-while cycles. Even though we know examples of such special treatment (e.g. in-class member function bodies can see all class members, including the ones declared below), there's probably not much practical sense in doing it for do-while cycles.
In case of do-while these special treatment rules would also require finding a meaningful way of handling initialization of variables declared in this fashion. Note that in C++ language the lifetime of such variable is limited to one iteration of the loop, i.e. the variable is created and destroyed on each iteration. That means that for do-while cycle the variable will always remain uninitialized, unless you introduce some rule that would somehow move the initialization to the beginning of the loop body. That would be quite confusing in my opinion.
It would be very unnatural to have a declaration of i after the block and to then be able to access it in the block. Declaration in for and while are nice short-hands that give limited-scope use to a variable that is needed in the loop logic.
Cleaner to do it this way:
int i;
do {
i = get_data();
// whatever you want to do with i;
} while (i != 0);
This is because everything else follows the practice of declaring variables before you use them, eg:
public static void main(String[] args){
// scope of args
}
for(int i=1; i<10; i++){
// scope of i
}
{
...
int somevar;
//begin scope of var
...
//end of scope of var
}
This is because things are parsed top down, and because following this convention keeps things intuitive, thus why you can declare a while(int var < 10) because the scope of that var will be the area inside the loop, after the declaration.
The do while doesn't make any sense to declare a variable because the scope would end at the same time it would be checked because that's when that block is finished.
Add this
#define do(cond) switch (cond) do default:
at the beginning of your code.
Now, you can write
do (int i = get_data())
{
// your code
} while ((i = get_data()));
It is important that this #define does not break the original syntax of the do keyword in do-while loop.
However, I admit that it is obscure.
Your first syntax is valid while the second is not.
However, your while loop will loop forever, even if your function get_data() returns 0.
Not sure if that's exactly what you want to happen.
for (int k = 0; k <= CONST; k++) {
for (int i = 0; i < CONST; i++) { <=== THIS ROW
if (...){
for (unsigned int i = 0; i < CONST; i++) { <=== AND THIS ONE
if (...) {
...
}
}
if (...) {
...
}
else {
...
}
}
}
}
the two rows I mentioned were not reconized as a compilation error, why is that?
thanks
The i declared in the inner loop hides the i declared in the outer loop for the duration of its scope. I was curious (never knew that was allowed!), so I dug up the relevant part of the standard:
(3.3.1)
The scope of a declaration is the same as its potential scope unless
the potential scope contains another declaration of the same name. In
that case, the potential scope of the declaration in the inner
(contained) declarative region is excluded from the scope of the
declaration in the outer (containing) declarative region.
While this is allowed by the standard (and thus all conforming compilers), it is bad practice because you, as the programmer, have to keep track of which variable named i you are referring to at different points in the code! Additionally, you have no way of accessing the outer loop's i in the inner loop.
The i declared in inner loop hides the declaration of i in the second for loop.
The inner i just hides outer variable i. It's not an error, just discouraged.
You have no way to access outer "int i" in the third for loop.
On the one hand according to the C++ Standard
If the name is re-declared in the outermost block of a substatement controlled by the
condition, the declaration that re-declares the name is ill-formed
On the other hand
1 The for statement for ( for-init-statement conditionopt;
expressionopt) statement is equivalent to
{
for-init-statement
while ( condition ) {
statement
expression ;
}
}
As it seen the for-init-statement and the condition are not declared in the outermost block of the preceding for statement if to nest one for statement inside the other for statement.
That is because of the scope of these two different variables. Inside one has the scope limited to the innermost for loop and outside one has the scope till its for loop. But outside i doesn't interfere while creating a local (inside) i because inside, whenever i is called, it calls the local one relative to itself primarily, not the outside/global one. It somehow follows the hierarchy this way since you have redefined the variable.
So, to call the global i, use this operator ::, which is scope resolution operator before the variable.
Is there a way to declare, assign and compare a variable to an expression in an if construction in such a way that it is only defined in the scope of the if construction?
This works (declare and assign, but of course the condition is only the return value of function f being equal to zero or not):
int main()
{
if(int i = f())
{
printf("%d", i);
// i is defined here!
}
else
{
// Here too!
}
// But not here!
}
But when I try to compare the value of i with an actual expression I run into trouble:
int main()
{
// Does not compile because "int i = f()" is not a primary expression?
if((int i = f()) == 3)
{
printf("%d", i);
}
}
Creating a scope around the whole if construction kind of does the trick, from a behavior point of view, but it looks ugly in my opinion:
int main()
{
{
int i = f();
if(i == 3)
{
printf("%d", i);
// i is defined here!
}
// here too!
}
// i is not defined here!
}
I'm comparing with the look and feel of for-loops and switches where it's so neat to declare and assign a variable so it is only defined in the scope in question. Of course the value of the variable is not compared to anything there and i'm not saying it's the same thing:
for(int i = 0;;)
{
break;
}
// i is not defined here
switch(int i = f())
{
default: break;
}
// i is not defined here!
So to sum it up, is there a way to tie the definition of a variable to the scope of "if" in a similar way as it's commonly tied to the scope of for and (perhaps not as commonly) switch, and where you actually compare the value of the variable to an expression as the condition of the if-statement?
As far as I can tell there is no way to have both a declaration and an expression within the condition of an if statement. If we look at the draft C++ standard section 6.4 Selection statements the grammar for if is as follows:
selection-statement:
if ( condition ) statement
if ( condition ) statement else statement
switch ( condition ) statement
condition:
expression
attribute-specifier-seqopt decl-specifier-seq declarator = initializer-clause
attribute-specifier-seqopt decl-specifier-seq declarator braced-init-list
So you either can use an expression or a declaration and I don't see any obvious ways around that.
What you proposed in the alternative, declaring i before the if statement seems like the best option. Although using an enclosing block does not seem necessary:
int i = f();
if(i == 3)
You can declare the variable in the if, but if you do so, the
condition depends on the implicit conversion of the type to
bool, which is very limiting and a bit obfuscating. The
definition in the for works because the first part of the
for isn't part of the condition. Just write:
int i = f();
if ( i == 3 ) {
// ...
} else {
// ...
}
This leaves the variable in scope until the end of the enclosing
block, but the enclosing block can't be bigger than the entire
function; if leaving it in scope is a problem, then your
functions are too large and complex, and need to be refactored.
You can only do a declaration OR boolean logic in an if statement. The C++ spec says so somewhere, forgot where exactly. Therefore a code like:
if (int x = 3 && x == 3) {} will never compile because it will also throw the error that x is used uninitialized
The first snippet works because you are declaring and defining i in if's conditional expression. It will always evaluated to true.
The second snippet does not compile because you are comparing a non-primary (because i is declared and defined here) expression in conditional expression with a primary expression.