53

I recently just lost some time figuring out a bug in my code which was caused by a typo:

if (a=b)

instead of:

if (a==b)

I was wondering if there is any particular case you would want to assign a value to a variable in a if statement, or if not, why doesn't the compiler throw a warning or an error?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Smash
  • 3,722
  • 4
  • 34
  • 54
  • 4
    I depends, my compiler throws a warning when a = b –  Jul 16 '13 at 16:03
  • 3
    The compiler doesn't issue a diagnostic because it is not mandated to do so. It is perfectly valid C++ code. – Alok Save Jul 16 '13 at 16:05
  • Should be noted that with warnings on you can typically figure this out by compile time. – Rapptz Jul 16 '13 at 16:06
  • 6
    Most compilers will warn about this, as long as you enable the warning. – Mike Seymour Jul 16 '13 at 16:07
  • 1
    It's not very useful in an `if` statement because you can just put the assignment in the previous line. It's more useful in a `while` condition, but still not usually recommended. – interjay Jul 16 '13 at 16:10
  • I agree that the other question touches on the same topic, but it is not a duplicate. – Adrian McCarthy Jul 16 '13 at 16:25
  • 1
    `while(*str1++ = *str2++);` <- string copy in C – Ed S. Jul 16 '13 at 19:07
  • While in a `if` statement it may not be "useful", in an `else if` it could be. – Spidey Aug 26 '13 at 15:36
  • For those using g++ who don't want to turn on all warnings but want them for just this specific case use -Wparentheses. – Michael Warner Jan 13 '16 at 16:07
  • 3
    I vote against treating this as a duplicate of https://stackoverflow.com/q/151850/2932052, because this is an actual C/C++ question that arises often whereas the other is very vague and language-agnostic. Maybe it's really *only* a C/C++ problem? In this case the other should get the tag and this question should be treated as duplicate. – Wolf Apr 23 '19 at 09:27
  • Another anti-duplicate argument: this is restricted explicitly to `if`, where assignment is often only a typo in C/C++. – Wolf Apr 23 '19 at 09:43
  • 1
    I agree with @Wolf. The dupe target is wrong, and should maybe even be reversed. Reopened. – cigien Oct 26 '20 at 01:27
  • 1
    Does this answer your question? [Inadvertent use of = instead of ==](//stackoverflow.com/q/399792/90527) – outis Jun 30 '22 at 05:42
  • @outis: That was the older duplicate [I was looking for](https://chat.stackoverflow.com/transcript/message/54816915#54816915)! (For some reason, search engines hesitate returning older Stack Overflow questions.). – Peter Mortensen Jun 30 '22 at 16:54
  • Due to the question being locked? Thus less (internal) link juice? It does not turn up in "Linked" panel on the right (neither in [the full list](https://stackoverflow.com/questions/linked/17681535)). – Peter Mortensen Jun 30 '22 at 17:04
  • [One from 2010](https://stackoverflow.com/questions/3122284/in-which-case-ifa-b-is-a-good-idea) – Peter Mortensen Jun 30 '22 at 17:32

9 Answers9

66
if (Derived* derived = dynamic_cast<Derived*>(base)) {
   // do stuff with `derived`
}

Though this is oft cited as an anti-pattern ("use virtual dispatch!"), sometimes the Derived type has functionality that the Base simply does not (and, consequently, distinct functions), and this is a good way to switch on that semantic difference.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • 17
    The anti-pattern is putting a definition in the condition. It's a sure recipe for unreadable and unmaintainable code. – James Kanze Jul 16 '13 at 16:25
  • 71
    Putting the definition in the condition tightens the scope so that you don't accidentally refer to `derived` on the next line after the if-statement. It's the same reason we use `for (int i = 0...` instead of `int i; for (i = 0; ...`. – Adrian McCarthy Jul 16 '13 at 16:36
  • 57
    @JamesKanze - Others find it increases readability and maintainability, partly by minimizing the scope of the variable. "To avoid accidental misuse of a variable, it is usually a good idea to introduce the variable into the smallest scope possible. In particular, it is usually best to delay the definition of a variable until one can give it an initial value ... One of the most elegant applications of these two principles is to declare a variable in a conditional." -- Stroustrup, "The C++ Programming Language." – Andy Thomas Jul 16 '13 at 16:40
  • 1
    However, the question is about whether you'd want assignment (to an already declared variable) in the condition; not about whether you'd want a declaration there. In my opinion, there's no good reason for assignment there. – Mike Seymour Jul 16 '13 at 17:05
  • 13
    @James: Nobody in my team has had a problem either reading or maintaining the code, in the ten years our codebase has taken this approach. – Lightness Races in Orbit Jul 16 '13 at 17:42
  • 3
    @AndyThomas Interesting quote. This is one place where I disagree with Stroustrup. It _is_ elegant. Too elegant, to the point of being obfuscating. The key rule in the quote is that you don't declare a variable until you can give it an initial value. With regards to defining the variable, there are two problems: the first is that it requires an implicit type conversion (unless it has type `bool`)---implicit conversions are bad; and the second is that it results in the code doing two things in a context where only one is expected. – James Kanze Jul 17 '13 at 08:01
  • 4
    "if (I succeeded in obtaining this value) { use this value }" seems fine to me. – Lightness Races in Orbit Jul 17 '13 at 09:04
  • 15
    Funny thing about the scope of the variable when putting a definition in the condition is that the variable is also accessible in the `else`-clause as well. `if (T t = foo()) { dosomething(t); } else { dosomethingelse(t); }` – dalle Jul 18 '13 at 08:57
  • @dalle: I didn't even know that. :) You're right, though (`6.4/3`). – Lightness Races in Orbit Jul 18 '13 at 09:07
  • Best example for this ever. – Didac Perez Parera Aug 26 '13 at 15:36
  • +1: The Clang compiler uses this kind of code in lots of places. (Except they use their home-cooked `dynamic_cast`.) – Thomas Eding Aug 26 '13 at 18:28
  • 2
    Fixed in C++17 `if (Derived* derived = dynamic_cast(base); derived != nullptr) {` - it's now explicitly supported to do an assignment and a conditional in a single step. The difference is that the compiler knows that if you've done it in this form, you explicitly mean to. Where as it can't know `if (a = b)` is an error or something the user meant. – gremwell Sep 28 '17 at 05:51
  • 1
    @gremwell Perhaps not the best counter-example, since `a = b` is not a declaration (so unaffected by & unrelated to the C++17 syntax you mention), and if you change the example to `if (T a = b)` then you _do_ indeed know that the user meant it because `T a == b` makes no sense. – Lightness Races in Orbit Jan 29 '19 at 17:50
  • I see what you're saying. Point was that I can see a future where they make assignment in conditionals a compiler error unless it's of that specific form. As you say, it'd be a bit silly to have `if (a=b; b != nullptr)` - however you're not likely to write that accidentally, where as if you miss a single `=` character and end up with `if (a=b)` you have an assignment in a conditional that you didn't mean. To me that should be a compiler error, and the explicit syntax is your way of telling the compiler that it's intended. – gremwell Jan 30 '19 at 22:57
  • @gremwell That part is true – Lightness Races in Orbit Jan 31 '19 at 00:53
  • @JamesKanze, huh. I find it more readable if anything. Makes the if block self contained. – sprajagopal Apr 19 '21 at 06:23
  • @LightnessRacesinOrbit, I think that `if (SomeType* someObj = dynamic_cast(libObj.optMember))` shows the pattern from a more pragmatic POV. So the application doesn't trick encapsulation, but combines a pointer check with **scope minimized access** to the functionality of `SomeType` which is derived from the type used in the API docs of `LibType::optMember` and that's actually used in the application. (I recently stumbled upon this pattern and remembered this post) – Wolf Oct 01 '21 at 08:44
50

Here is some history on the syntax in question.

In classical C, error handling was frequently done by writing something like:

int error;
...
if(error = foo()) {
    printf("An error occurred: %s\nBailing out.\n", strerror(error));
    abort();
}

Or, whenever there was a function call that might return a null pointer, the idiom was used the other way round:

Bar* myBar;
... // In old C variables had to be declared at the start of the scope
if(myBar = getBar()) {
    // Do something with myBar
}

However, this syntax is dangerously close to

if(myValue == bar()) ...

which is why many people consider the assignment inside a condition bad style, and compilers started to warn about it (at least with -Wall). Some compilers allow avoiding this warning by adding an extra set of parentheses:

if((myBar = getBar())) {  // It tells the compiler: Yes, I really want to do that assignment!

However, this is ugly and somewhat of a hack, so it's better avoid writing such code.

Then C99 came around, allowing you to mix definitions and statements, so many developers would frequently write something like

Bar* myBar = getBar();
if(myBar) {

which does feel awkward. This is why the newest standard allows definitions inside conditions, to provide a short, elegant way to do this:

if(Bar* myBar = getBar()) {

There isn't any danger in this statement anymore. You explicitly give the variable a type, obviously wanting it to be initialized. It also avoids the extra line to define the variable, which is nice. But most importantly, the compiler can now easily catch this sort of bug:

if(Bar* myBar = getBar()) {
    ...
}
foo(myBar->baz);  // Compiler error
myBar->foo();     // Compiler error

Without the variable definition inside the if statement, this condition would not be detectable.

To make a long answer short: The syntax in you question is the product of old C's simplicity and power, but it is evil, so compilers can warn about it. Since it is also a very useful way to express a common problem, there is now a very concise, bug robust way to achieve the same behavior. And there are a lot of good, possible uses for it.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
cmaster - reinstate monica
  • 38,891
  • 9
  • 62
  • 106
  • 1
    But adding extra parenthesis is ***not*** "bug robust": https://www.approxion.com/a-gcc-compiler-mistake/ The MISRA-compliant code `if ((a == b) && (c = d))` won't generate a warning or error because of GCC's "feature". Instead of "bug robust" it's perniciously ***buggy***, You *think* using `-Wall -Werror` protects you from such bugs, but it actually **doesn't**. – Andrew Henle Jun 06 '21 at 12:12
  • 2
    @AndrewHenle Well, that part about assignment in extra brackets was only to illustrate what has been done many times in real existing code. *Of course*, the extra parentheses are only a hack. *Of course*, they look ugly. *Of course*, this is just an extra reason to prefer the C++ variable declaration over a C assignment in an `if()` statement. You may question the sensibility of GCC's warning behavior, but that has no impact on my answer at all. – cmaster - reinstate monica Jun 06 '21 at 13:16
  • 1
    The issue isn't with your answer - I think it's about the best possible factual discussion of the history of the "assignment in an if-statement" style came about. The problem is your answer was referred by someone who utterly missed your characterization of GCC's hack and actually used your answer as a *defense* of that hack. I was pointing out how GCC's hack actually opens the door to even more subtle bugs when more complex code is involved. That evil aspect of the GCC "extra parentheses" hack seems to be utterly missed by proponents of cramming assignments into if statements. – Andrew Henle Jun 06 '21 at 16:12
  • 3
    @AndrewHenle Thanks for the explanation. It gets me thinking that it might be beneficial to point out that this is a hack, and that I discourage using it. – cmaster - reinstate monica Jun 06 '21 at 18:44
21

The assignment operator returns the value of the assigned value. So, I might use it in a situation like this:

if (x = getMyNumber())

I assign x to be the value returned by getMyNumber and I check if it's not zero.

Avoid doing that. I gave you an example just to help you understand this.

To avoid such bugs up to some extent, one should write the if condition as if(NULL == ptr) instead of if (ptr == NULL). Because when you misspell the equality check operator == as operator =, the compiler will throw an lvalue error with if (NULL = ptr), but if (res = NULL) passed by the compiler (which is not what you mean) and remain a bug in code for runtime.

One should also read Criticism regarding this kind of code.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Maroun
  • 94,125
  • 30
  • 188
  • 241
  • 14
    I want to learn/improve myself, please mention why you downvote. – Maroun Jul 16 '13 at 16:16
  • 2
    I frequently use `if(fp = fopen("file", "w")){ // file not open} else{ // file processing }` – Grijesh Chauhan Jul 16 '13 at 19:00
  • 18
    Reversing the operands of an equality comparison, such as writing `(NULL == ptr)` rather than `(ptr == NULL)`, is controversial. Personally, I *hate* it; I find it jarring, and I have to mentally re-reverse it to understand it. (Others obviously don't have that issue). The practice is referred to as ["Yoda conditions"](http://en.wikipedia.org/wiki/Yoda_Conditions). Most compilers can be persuaded to warn about assignments in conditions anyway. – Keith Thompson Jul 16 '13 at 19:30
  • 1
    I think its nice to know the trick! I added as an information, Some people may like it. That is why its just a suggestion. – Grijesh Chauhan Jul 16 '13 at 20:01
  • @GrijeshChauhan Thanks a lot for your valuable edit. You're always there to make answers better..! – Maroun Jul 16 '13 at 20:25
  • Please do not suggest this awful syntax. In reality this is a non-issue. Also, compilers now throw a warning for this case. – Lilian A. Moraru Oct 17 '17 at 08:33
  • 1
    @LilianA.Moraru For some it is helpful. That's why it's a suggestion, if you don't like it you don't have to use it. – Maroun Oct 17 '17 at 08:36
  • @MarounMaroun Ye, that's the way. Suggest something awful to people and when somebody brings an argument with examples, just say "if you don't like it, don't use it"... Should of said this from beginning then, not after you suggested this crap. – Lilian A. Moraru Oct 18 '17 at 05:24
  • 1
    @LilianA.Moraru The word "suggestion" implies whatever you said. BTW, if you look at the edit history, you'll note that it wasn't me who added this suggestion. However, since it's only a suggestion, if you don't like it, skip and move on. – Maroun Oct 18 '17 at 05:43
  • 1
    I would also add _don't use `NULL`, use `nullptr` instead!_ – Enlico Nov 11 '20 at 16:57
  • "assignment operator returns the value of the assigned value" really!? Is this the only answer that mentions that? – KcFnMi Oct 26 '22 at 02:50
13

why doesn't the compiler throw a warning

Some compilers will generate warnings for suspicious assignments in a conditional expression, though you usually have to enable the warning explicitly.

For example, in Visual C++, you have to enable C4706 (or level 4 warnings in general). I generally turn on as many warnings as I can and make the code more explicit in order to avoid false positives. For example, if I really wanted to do this:

if (x = Foo()) { ... }

Then I'd write it as:

if ((x = Foo()) != 0) { ... }

The compiler sees the explicit test and assumes that the assignment was intentional, so you don't get a false positive warning here.

The only drawback with this approach is that you can't use it when the variable is declared in the condition. That is, you cannot rewrite:

if (int x = Foo()) { ... }

as

if ((int x = Foo()) != 0) { ... }

Syntactically, that doesn't work. So you either have to disable the warning, or compromise on how tightly you scope x.

C++17 added the ability to have an init-statement in the condition for an if statement (p0305r1), which solves this problem nicely (for kind of comparison, not just != 0).

if (x = Foo(); x != 0) { ... }

Furthermore, if you want, you can limit the scope of x to just the if statement:

if (int x = Foo(); x != 0) { /* x in scope */ ... }
// x out of scope
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Adrian McCarthy
  • 45,555
  • 16
  • 123
  • 175
  • The explicit comparison `if((x = Foo()) != 0)` is unnecessary: adding an extra pair of parentheses `if((x = Foo()))` will silence the warning just as well. Also, there is a big difference between `if(x = Foo())` and `if(int x = Foo())`, the later clearly states that you want to do an initialization, not a comparison, so no compiler in their right mind would want to throw a warning on this. – cmaster - reinstate monica Aug 26 '13 at 17:35
  • 2
    To be fair, you don't have to compromise how tightly you scope `x` if you are willing to surround your block with extra braces. But I wager this is not usually worth it. – Thomas Eding Aug 26 '13 at 18:30
  • 2
    @cmaster: Explicit comparison is necessary if you want to check something other than `!= 0`. Explicit comparison is also clearer than an extra set of parentheses. – Adrian McCarthy Aug 26 '13 at 20:33
  • 1
    @AdrianMcCarthy: Explicit comparison also requires that extra set of parentheses (due to operator precedence). So the addition of ` != 0` is always just extra code. Of course, you are right that explicit comparison can compare to anything. If you are comparing against 0, however, I heartily disagree with you: This is such a common idiom that I expect the shorter version in that case, so that the extra ` != 0` has the effect of a small pothole to me... – cmaster - reinstate monica Aug 26 '13 at 20:53
  • 1
    @cmaster: That may be the case for you. But other programmers may be confused and think the extra parentheses are superfluous. They could delete them (which might not bother the compiler if they haven't enabled the same warning). Then someone will come along and think the assignment was supposed to be `==`, and now you've got two bugs. The explicit comparison makes it obvious clear _why_ the extra parentheses are there and, more importantly, that the assignment was not a typo. – Adrian McCarthy Mar 27 '19 at 12:11
13

In C++17, one can use:

if (<initialize> ; <conditional_expression>) { <body> }

Similar to a for loop iterator initializer.

Here is an example:

if (Employee employee = GetEmployee(); employee.salary > 100) { ... }
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
DevBro
  • 131
  • 1
  • 2
8

It depends on whether you want to write clean code or not. When C was first being developed, the importance of clean code wasn't fully recognized, and compilers were very simplistic: using nested assignment like this could often result in faster code. Today, I can't think of any case where a good programmer would do it. It just makes the code less readable and more difficult to maintain.

James Kanze
  • 150,581
  • 18
  • 184
  • 329
  • 1
    I agree that it's less readable but only if the assignment is the only thing in the if statement. If it's something like `if ((x = function()) > 0)` then I think that's perfectly fine. – ashishduh Mar 24 '14 at 19:29
  • 2
    @user1199931 Not really. If you're scanning the code to get a general understanding of it, you'll see the `if` and continue, and miss the fact that there is a change of state. There are exceptions (`for`, but the keyword itself says that it is both looping and managing the loop state), but in general, a line of code should do one, and only one thing. If it controls flow, it shouldn't modify state. – James Kanze Mar 26 '14 at 13:41
  • 3
    "Clean code" is a very subjective term, unfortunately. – Michael Warner Jan 11 '16 at 22:01
6

Suppose you want to check several conditions in a single if, and if any one of the conditions is true, you'd like to generate an error message. If you want to include in your error message which specific condition caused the error, you could do the following:

std::string e;
if( myMap[e = "ab"].isNotValid() ||
    myMap[e = "cd"].isNotValid() ||
    myMap[e = "ef"].isNotValid() )
{
    // Here, 'e' has the key for which the validation failed
}

So if the second condition is the one that evaluates to true, e will be equal to "cd". This is due to the short-circuit behaviour of || which is mandated by the standard (unless overloaded). See this answer for more details on short-circuiting.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
elatalhm
  • 516
  • 3
  • 12
  • Interesting, but isn't it also for showing cleverness? – Wolf Apr 23 '19 at 09:50
  • Using `const char* e;` instead of `std::string e;` would make it faster, but the similarity of the lines seems to ask for a loop over `{"ab", "cd", "ef"};`. – Wolf Oct 01 '21 at 10:15
2

Doing assignment in an if is a fairly common thing, though it's also common that people do it by accident.

The usual pattern is:

if (int x = expensive_function_call())
{
  // ...do things with x
}

The anti-pattern is where you're mistakenly assigning to things:

if (x = 1)
{
  // Always true
}
else
{
  // Never happens
}

You can avoid this to a degree by putting your constants or const values first, so your compiler will throw an error:

if (1 = x)
{
  // Compiler error, can't assign to 1
}

= vs. == is something you'll need to develop an eye for. I usually put whitespace around the operator so it's more obvious which operation is being performed, as longname=longername looks a lot like longname==longername at a glance, but = and == on their own are obviously different.

tadman
  • 208,517
  • 23
  • 234
  • 262
  • 10
    They're both anti patterns. And as for `if ( 1 == x )`, it's not the natural way of expressing the test, and any compiler worth its salt will warn for `if ( x = 1 )`, so there's really no reason for being ugly here either. – James Kanze Jul 16 '13 at 16:10
  • 1
    The `1 == x` pattern, anti or not, comes from [Code Complete](http://en.wikipedia.org/wiki/Code_Complete) so you'll see it fairly often. It's a bit ugly, but might be a reasonable requirement for people that have a problem getting this right, serving as training wheels. – tadman Jul 16 '13 at 16:17
  • 3
    @tadman The way to teach people is by annoying them with warnings about this typo, not to switch to yoda conditions. – stefan Jul 16 '13 at 16:18
  • 2
    @stefan Especially as most compilers have an option to turn warnings into errors. – James Kanze Jul 16 '13 at 16:21
  • @tadman: And reasonable compiler warnings catch more instances of this mistake than Yoda conditions do. And Yoda conditions only help when you remember to use them. – Adrian McCarthy Jul 16 '13 at 16:22
  • @tadman There are, I fear, a lot of bad advice that slips into otherwise good books. And people follow blindly, instead of thinking. (I often get the impression that the only thing people adopt are the bad parts. A book can have 99 good ideas, and one bad, and everyone will adopt the bad, and ignore the other 99.) – James Kanze Jul 16 '13 at 16:23
  • @JamesKanze Indeed. I think being disciplined about recognizing these mistakes is important, and being able to perceive them is an important skill. That's why my suggestion for using whitespace to make them more obvious is my first recommendation, and `1 == x` is a last resort. – tadman Jul 16 '13 at 16:30
  • @tadman The white space is a good idea, in general. But tastes concerning this vary, and I've worked in shops where the rule was "no white space anywhere in an expression". I found it totally unreadable, but the people working there felt the same about my (very spaced out) code. – James Kanze Jul 17 '13 at 07:56
0

a quite common case. use

if (0 == exit_value)

instead of

if (exit_value == 0)

this kind of typo will cause compile error

Kevin Chan
  • 59
  • 5