Initializing variables in an "if" statement

C++C++17

C++ Problem Overview


I read that in C++17 we can initialize variables in if statements like this

if (int length = 2; length == 2)
    //execute something

Instead of

int length = 2;
if (length == 2)
    //do something

Even though it is shorter, it affects the code readability (especially for people who don't know this new feature), which I suppose is a bad coding practice for large software development.

Is there any advantage of using this feature other than making the code shorter?

C++ Solutions


Solution 1 - C++

It limits the scope of length to the if alone. So you get the same benefits we originally got when we were allowed to write

for(int i = 0; i < ... ; ++i) {
   // ...
}

Instead of the variable leaking

int i;
for(i = 0; i < ... ; ++i) {
   // ...
}

Short lived variables are better for several reasons. But to name a couple:

  1. The shorter something lives, the less things you need to keep in mind when reading unrelated lines of code. If i doesn't exist outside the loop or if statement, then we don't need to mind its value outside of them. Nor do we need to worry its value will interact with other parts of the program that are outside of its intended scope (which may happen if i above is reused in another loop). It makes code easier to follow and reason about.

  2. If the variable holds a resource, then that resource is now held for the shortest period possible. And this is without extraneous curly braces. It's also made clear the resource is related to the if alone. Consider this as a motivating example

    if(std::lock_guard _(mtx); guarded_thing.is_ready()) {
    }
    

If your colleagues aren't aware of the feature, teach them! Appeasing programmers who don't wish to learn is a poor excuse to avoid features.

Solution 2 - C++

> Is there any advantage of using this feature other than making the code shorter?

You reduce variable scope. This does make sense and increases readability, as it strengthens the locality of identifiers you need to reason about. I agree that long init statements inside if statements should be avoided, but for short stuff, it's fine.

Note that you can already do initialization and branching on the result in pre-C++17:

int *get(); // returns nullptr under some condition

if (int *ptr = get())
    doStuff();

This is subject to one's personal opinion, but you can consider an explicit condition more readable:

if (int *ptr = get(); ptr != nullptr)
    doStuff();

Besides, arguing against the readability of a feature by referring to the fact that people aren't used to it is dangerous. People weren't used to smart pointers at some point, yet still we all agree today (I guess) that it's a good thing they are there.

Solution 3 - C++

The new form of the if statement has many uses.

> Currently, the initializer is either declared before the statement and > leaked into the ambient scope, or an explicit scope is used. With the > new form, such code can be written more compactly, and the improved > scope control makes some erstwhile error-prone constructions a bit > more robust.

Open Standard Proposal for If statement with initializer

enter image description here

So, in summary, this statement simplifies common code patterns and helps users keep scopes tight.

I hope it helps!

Solution 4 - C++

In the interest of minimizing the scope of variables there is an idiom which defines a resource only if it is valid upon creation (for example file stream objects):

if(auto file = std::ifstream("filename"))
{
    // use file here
}
else
{
    // complain about errors here
}

// The identifier `file` does not pollute the wider scope

Sometimes you want to be able to reverse the logic of that test to make the failure the primary clause and the valid resource the else clause. This was previously not possible. But now we can do:

if(auto file = std::ifstream("filename"); !file)
{
    // complain about errors here
}
else
{
    // use file here
}

An example might be throwing an exception:

if(auto file = std::ifstream(filename); !file)
    throw std::runtime_error(std::strerror(errno));
else
{
    // use file here
}

Some people like to code so that a function will abort early on an error and otherwise progress. This idiom puts the abort logic physically above the continuation logic which some people may find more natural.

Solution 5 - C++

It is especially useful for logical events. Consider this example:

char op = '-';
if (op != '-' && op != '+' && op != '*' && op != '/') {
	std::cerr << "bad stuff\n";
}

Seems a bit rough. Unless you are very familiar with OR, AND with negations, you might need to pause and think about this logic - which is generally poor design. With the if-initialization you can add expressiveness.

char op = '-';
if (bool op_valid = (op == '-') || (op == '+') || (op == '*') || (op == '/'); !op_valid) {
	std::cerr << "bad stuff\n";
} 

the named variable can be re-used inside the if too. E.g:

if (double distance = std::sqrt(a * a + b * b); distance < 0.5){
    std::cerr << distance << " is too small\n";
}

This is great, especially given that the variable is scoped and therefore doesn't pollute the space afterwards.

Solution 6 - C++

This is an extension of an existing feature, which aids readability in my experience.

if (auto* ptr = get_something()) {
}

Here we both create the variable ptr and we test against it being non-null. The scope of ptr is limited to where it is valid. It is far, far easier to convince yourself that all use of ptr is valid.

But what if we are talking about something that doesn't convert to bool that way?

if (auto itr = find(bob)) {
}

That doesn't work. But with this new feature we can:

if (auto itr = find(bob); itr != end()) {
}

Add a clause saying "when is this initialization valid".

In essence, this gives us a set of tokens that mean "initialize some expression, and when it is valid, do some code. When it is not valid, discard it."

It has been idiomatic to do the pointer-test trick since C++98. Once you have embraced that, this extension is natural.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionArneView Question on Stackoverflow
Solution 1 - C++StoryTeller - Unslander MonicaView Answer on Stackoverflow
Solution 2 - C++lubgrView Answer on Stackoverflow
Solution 3 - C++Abhishek SinhaView Answer on Stackoverflow
Solution 4 - C++GalikView Answer on Stackoverflow
Solution 5 - C++Stack DannyView Answer on Stackoverflow
Solution 6 - C++Yakk - Adam NevraumontView Answer on Stackoverflow