what's wrong with declaring a variable inside if's condition? - c++

Perhaps I am getting rusty (have been writing in Python recently).
Why does this not compile?
if ( (int i=f()) == 0)
without the () around the int i=f() I get another, much more reasonable error of i is not being boolean. But that's why I wanted the parentheses in the first place!
My guess would be that using the parentheses makes it into an expression, and that declaration statements are not allowed in an expression. Is it so? And if yes, is it one of the C++'s syntax quirks?
BTW, I was actually trying to do this:
if ( (Mymap::iterator it = m.find(name)) != m.end())
return it->second;

You can declare a variable in the if statement in C++ but it is restricted to be used with direct initialization and it needs to convert to a Boolean value:
if (int i = f()) { ... }
C++ doesn't have anything which could be described as "declaration expression", i.e. [sub-] expressions declaring a variable.
Actually, I just looked up the clause in the standard and both forms of initialization are supported according to 6.4 [stmt.select] paragraph 1:
...
condition:
expression
attribute-specifier-seqopt decl-specifier-seq declarator = initializer-clause
attribute-specifier-seqopt decl-specifier-seq declarator braced-init-list
...
That is, it is also be possible to write:
if (int i{f()}) { ... }
Obviously, this only works in C++2011 because C++2003 doesn't have brace-initialization.

There's a problem with scope.
Consider the following code:
if ((int a = foo1()) || (int b = foo2()))
{
bar(b);
}
Is b declared inside the block? What if foo1() returns true?

You can declare a variable in an if statement (or in for or while), but only in the outer parenthesis block and it needs to be able to be converted to bool.
Your guess is basically right, it's not allowed because
(int i = 42;)
is not a valid declaration with initialization.
You need one additional line,
Mymap::iterator it;
if ( (it = m.find(name)) != m.end())
return it->second;
but then it's better to write
Mymap::iterator it = m.find(name);
if ( it != m.end() )
return it->second;
You can put the return line after the if, if you really want this line back, at least for me this doesn't harm readability, but others might see that different.
If you really, really want to declare an iterator and use the it as bool in an if condition, you could do
if ( struct { int it; operator bool() { return it != m.end; } } s = { m.find(name) } )
return s.it->second;
but I would consider this harmful ;-)

It's true that you can't write
if ( (int i=f()) == 0)
but you can perfectly write
if ( int i=f())
So you can use the && operator to perform both operations in one statement like
if ( int i=1 && (i=f()) == 0)
i should be initialized with any value other than 0, and it should be the first condition if your compiler applies left-to-right evaluation.
But unfortunately, that's not applicable in case of iterators as your second example asks.

Related

Is it defined behaviour to assign to function call in or in if (C++17)

During a codebase refactor I found code like this:
void myFunction (std::map<int, int> my_map)
{
int linked_element;
if (my_map[linked_element = firstIndex] != 0
|| my_map[linked_element = secondIndex] != 0)
{
// do some stuff with linked_element
}
}
Or
void myFunction (std::set<int> my_set)
{
int linked_element;
if (my_set.find(linked_element = firstIndex) != my_set.end()
|| my_set.find(linked_element = secondIndex) != my_set.end())
{
// do some stuff with linked_element
}
}
From what I understood the aim of that was to avoid checking 2 times (first when entering in the if, second when assigning the variable).
I can understand that depending on which side of the || is true linked_element will be assigned to the right value but this still feels kind of bad to me.
Is this kind of behaviour defined?
This behavior is well defined by the order of evaluation.
First, the linked_element = firstIndex assignment happens. This expression returns the value of firstIndex, that is then used as an argument for the subscript operator on my_map (i.e., my_map[linked_element = firstIndex]). The return value from that expression is checked against the != 0 condition. If it's true, the other side of the || operator is not evaluated due to short-circuit logic. If it's false, the same story happens on the other side of the operator.
Whether or not it's a good practice to write code in such a style is a different question though. Personally speaking, I'd prioritize readability and maintainability over this micro-optimization unless it's a super-critical piece of the program, but it's a matter of opinion, I guess.
In original code behavior is well defined, since operator || evaluates first argument and if this is evaluated to false evaluates second argument.
BUT: Assignment there is confusing and many (probably all) static analyzes tools will complain about this. So I would reflector this code in this way, so it would require less brain power to read:
void doSomeStuff(const std::set<int>& my_set, int linked_element)
{
.....
}
void myFunction (const std::set<int>& my_set)
{
if (my_set.find(firstIndex) != my_set.end())
{
doSomeStuff(my_set, firstIndex);
} else if (my_set.find(secondIndex) != my_set.end()) {
doSomeStuff(my_set, secondIndex);
}
}
Since you had to ask question about this code this proves that original version is bad from maintainer point of view. Code which requires lots of focus to understand is costly in maintenance.
BTW this fragment of code:
if (my_map[linked_element = firstIndex] != 0
looks suspicious. I have even more suspensions seeing set-version.
This looks like that someone do not understand how operator[] works for maps. If value for key do not exist, default value is introduced to map. So checking for default value 0 seem like attempt to adders this issue. Possibly my_map.count(firstIndex) should be used.
An alternate version, assuming firstIndex and secondIndex are literal values (like 2 and 7), or are otherwise known relative to some invalid third index value:
void myFunction (std::set<int> & my_set)
{
int linked_element =
my_set.contains (firstIndex) ? firstIndex :
my_set.contains (secondIndex) ? secondIndex :
thirdIndex;
if (linked_element != thirdIndex)
{
// do some stuff with linked_element
}
}
If the indices are not known then a std::optional<int> can step in here too.
If pre-C++20, replace .contains() with .count().
Bigger concerns with the original code are:
the pass-by-value of a potentially large container (never assume COW)
map[index] silently adds the index to the map if not present

C++11 auto /I dont understand something

Ok. this is my code :
CShop::~CShop()
{
TPacketGCShop pack;
pack.header = HEADER_GC_SHOP;
pack.subheader = SHOP_SUBHEADER_GC_END;
pack.size = sizeof(TPacketGCShop);
Broadcast(&pack, sizeof(pack));
GuestMapType::iterator it;
it = m_map_guest.begin();
while (it != m_map_guest.end())
{
LPCHARACTER ch = it->first;
ch->SetShop(NULL);
++it;
}
M2_DELETE(m_pGrid);
}
Soo i have GuestMapType::iterator it; and this it = m_map_guest.begin();
It'f fine if i make my function like this?
CShop::~CShop()
{
TPacketGCShop pack;
pack.header = HEADER_GC_SHOP;
pack.subheader = SHOP_SUBHEADER_GC_END;
pack.size = sizeof(TPacketGCShop);
Broadcast(&pack, sizeof(pack));
auto it = m_map_guest.begin();
while (it != m_map_guest.end())
{
LPCHARACTER ch = it->first;
ch->SetShop(NULL);
++it;
}
M2_DELETE(m_pGrid);
}
I removed GuestMapType::iterator it; to simplify my code?
My question is. Affect this my program? Any risk ?
That's perfectly fine and declaring iterators with auto is, from my view, a good practice for at least two reasons:
1- Generally, iterator's type are pretty long to type. The less you type, the less you mis-type. It makes the code clearer too, because you hide an implementation detail that doesn't really matter, in that context.
2- Forward compatibility: when you will modify your code, i.e. the name for the iterator type name, you won't have to change the code where you use it with auto. After all, you want to use that type of iterator, independently from its name.
Provided the return type of m_map_guest.begin() can be determined to be GuestMapType::iterator, there should be no difference.
The auto typing is not some magical dynamic/weak type, it's simply an indicator to the compiler that it should work out the strong type itself, without you being explicit about it1. There is no functional difference between the two lines:
int i = 7;
auto i = 7;
because the compiler uses the type of the 7 in the second example to infer the actual type of i.
You should generally use auto wherever possible for the same reason you should use the second of these in C:
ttype *x = malloc(sizeof (ttype));
ttype *x = malloc(sizeof (*x));
The latter requires only one change if the type ever changes to something else - that becomes much more important with auto as the actual type and declaration can be much more widely separated.
1 Of course, where the return type is different, such as implicit conversions or sub-classes, it's a little harder for the compiler to work out what type you want. So it may not work in those cases.

Declaring several new counters in a for loop

Consider the following code:
vector<int> v;
for(vector<int>::iterator vi = n.begin(), int i = 0;
vi != n.end();
++vi, ++i){}
Is there a reason why this is not allowed? I want to be able to define 2 new counters, both vi and the index i.
This is the explanation from the book C++ Primer:
As in any other declaration, init-statement can define several objects. However, init-statement may be only a single declaration statement. Therefore, all the variables must have the same base type. As one example, we might write a loop to duplicate the elements of a vector on the end as follows:
// remember the size of v and stop when we get to the original last element
for (decltype(v.size()) i = 0, sz = v.size(); i != sz; ++i)
v.push_back(v[i]);
In this loop we define both the index, i, and the loop control, sz, in init-statement.
This makes sense, the syntax of for loop is:
C++11 §6.5.3 The for statement [stmt.for]
The for statement
for ( for-init-statement ; condition opt ; expression opt ) statement
for-init-statement is one statement only. Declaration two different types of variables would make it at least two statements.
Is there a reason why this is not allowed?
Because the arcane declaration syntax of C++ doesn't allow you to declare objects of unrelated types in the same declaration statement; and the initialiser of a for loop only allows a single declaration statement.
I want to be able to define 2 new counters, both vi and the index i.
You could declare one or both outside the loop, if you don't mind polluting the surrounding block. Otherwise, you could put them in a stucture:
for (struct {vector<int>::iterator vi; int i;} x = {n.begin(), 0};
x.vi != n.end();
++x.vi, ++x.i) {}
The answer is "there isn't really any reason other than the syntax requires it".
I can imagine, though, that code could get very complex if it were allowed, so that's a good reason not to add support for this into the language.
You can create your own scope to bound it instead:
std::vector<int> v;
{
std::vector<int>::iterator it = n.begin(), end = n.end();
int i = 0;
for ( ; it != end; ++it, ++i)
{}
}
If you want to use two variables of differing type in a for loop, one must be declared outside the scope of the for loop. You can enforce the scope of the second one by enclosing the loop inside a set of braces:
vector<int> v;
{
int i = 0;
for(vector<int>::iterator vi = n.begin(); vi != n.end(); ++vi, ++i) { /* DO STUFF */ }
} //i's scope ends here.
You can only write one declaration statement, but it
can define multiple variables, e.g.:
for ( int i = 0, j = 10 ; ... )
A look at the comma operator wikipedia page would help, specifically the first example.
int a=1, b=2, c=3, i=0; // comma acts as separator in this line, not as an operator
Also, why not do something like this?
vector<int> v;
vector<int>::iterator vi = n.begin();
int i = 0;
for(; vi != n.end(); ++vi, ++i)
{
}

How can I check if the key exists in a std::map and get the map::iterator in if condition?

I'd like to define the variable in condition expression so that the variable scope would be within the if clause. This works fine,
if (int* x = new int(123)) { }
When I was trying to do a similar thing with map::iterator,
if ((map<string, Property>::iterator it = props.find(PROP_NAME)) != props.end()) { it->do_something(); }
I got error: expected primary-expression before ‘it’
What makes the difference between int* and map::iterator?
There's no difference between int * and map::iterator in that regard. There's a difference in the surrounding semantic constructs that you are using with int * and map::iterator, which is why one compiles and other doesn't.
With if you have a choice of either
if (declaration)
or
if (expression)
Declaration is not an expression. You can't use a declaration as a subexpression in a larger expression. You cannot use a declaration as a part of explicit comparison, which is exactly what you attempt to do.
For example, if you attempted to do the same thing with int *, like this
if ((int* x = new int(123)) != NULL)
the code would not not compile for exactly the same reasons your map::iterator code does not compile.
You have to use
if (int* x = new int(123))
or
int* x = new int(123);
if (x != NULL)
or
int* x;
if ((x = new int(123)) != NULL)
As you can see above, int * exhibits exactly the same behavior as map::iterator.
In your example, it is impossible to declare it and perform its comparison with props.end() in ifs condition. You will have to use one of the above variants instead, i.e. either
map<string, Property>::iterator it = props.find(PROP_NAME);
if (it != props.end())
or
map<string, Property>::iterator it;
if ((it = props.find(PROP_NAME)) != props.end())
Choose whichever you like more.
P.S. Of course, formally you can also write
if (map<string, Property>::iterator it = props.find(PROP_NAME))
but it does not do what you want it to do (does not compare the iterator value to props.end()) and might not compile at all, since the iterator type is probably not convertible to bool.
Here is one way to limit it to a scope:
{
auto it = props.find(PROP_NAME);
if (it != props.end()) {
it->do_something();
}
}
Granted, this scope is not technically the "if scope", but should serve just as well for all practical intents and purposes.
As AndreyT already explained (+1), declaration can't transcend ( and ), which you did not use for int but you did for the iterator.
Map iterators contain first and second, which point to the key and value, respectively.
To access a member of the value, use it->second.do_Something()

How to show that a loop control variable is not changed inside the C++ for-loop body?

In C++ changing a loop variable inside a for-loop is allowed:
for( int i = 0; i < limit; i++ ) {
if( condition ) {
i--;
}
}
Now if a loop body is rather complex it is not immediately obvious to the reader whether the loop variable is changed inside the loop body. It would be nice to somehow tweak the code so that once the reader only sees the for-loop header he immediately knows that the loop variable is not changed inside the body.
For example, if I use const:
const int value = computeValue();
//lots of code here
then it is clear that whatever code is written below the const variable definition the variable is unchanged.
Is there a way to achieve something similar - logical constness within the iteration - in case of for-loop control variables in C++?
I don't think this is possible for hand-crafted loop, but I'd guess that could be considered an additional argument to encourage the use of std::for_each and BOOST_FOREACH for iterations over STL container.
EDIT ... and the C++0x range-based for-loop (thanks Matthieu. M :)
This is a great question and got me thinking about how you might do this and ways around it. Without spending too much time, the only thing I came up with was:
int limit = 10;
for(int i(0), guard(0); i < limit; i++, guard++)
{
// lots of code here
assert(i == guard);
}
Obviously, the user could still modify guard in the loop, but perhaps the inention at the top shows that it is going to be checked.
Use for_each combined with boost::counting_iterator and a function that accepts a const int.
for_each(boost::counting_iterator<int>(0), boost::counting_iterator<int>(limit),
[&](const int i)
{
// do something with i
});
C++0x fun. This code is not compiled:
for (int i = 0; i < 10; ++i)
{
[&, i] ()
{
if ( i == 5 )
{
++i;
}
cout << i << endl;
}();
}
Error:
'i': a by-value capture cannot be modified in a non-mutable lambda
There is no logical construct to enforce this. If you put ‘const int idx = i‘ as the first statement in the loop, and then only use ‘idx‘, you may be able to achieve a similar enforcing but at the loss of some clarity. Otherwise, just use comments.
You could make the entire body of the for loop a separate function, for which the loop control variable is out of scope.
I can't think of a simple way to do what you want since the loop control variable is be definition mutable, in order to control the loop.
Create a strange object with a macro that takes FILE and LINE, the latter possibly as a template parameter (is it a compile time constant?). As you increment it, it must be use the same FILE and LINE. Well the same line will probably suffice. If it is not on the same line you might get a compiler error.
template< int line >
class Counter
{
// stuff
public:
bool valid() const;
static void inc( Counter<line> & counter );
};
for( Counter<__LINE__> counter( n ); counter.valid(); Counter<__LINE__>::inc( counter ) )
{
// body
// what __LINE__ do I need to use here to increment counter? Can be done but won't be
}
I haven't tested it. Just an idea.
Technically you could do e.g.
int main()
{
for( int i = 0; i < 42; ++i )
{{
typedef void i;
i = 666; // !Nope.
}}
}
If you want access to i inside the loop body you'd then have to provide an alternate name (referring to const) before the typedef.
But I don't recommend this technical solution, because it's obscure and not common, so not obvious to reader.
Instead, just refactor big loops. :-)
You may try to change your "loop" variable name into something long and ridiculous, so that touching it inside the loop (more than once) will scratch the eye.
Also some like using dedicated loop macros, such as BOOST_FOREACH. This conceals the loop variable/iterator.