7

I want to declare a local variable in the brackets of an if statement. For example.

if((char c = getc(stdin)) == 0x01)//This is not OK with g++.
{
    ungetc(c, stdin);
}

What I want is, to see if the character is the one I want. To say it commonly, I want to use the variable(char c) both in the line of if and the body of if, but not outside the if.

But g++(GCC 4.8.1) says expected primary-expression before 'char'. I wonder if there's a way to do that, because I don't want something like

char c = getc(stdin);
if(c == 0x01)
{
    bla...
}
Eric Postpischil
  • 141,624
  • 10
  • 138
  • 247
Wesley
  • 441
  • 5
  • 18
  • 3
    You could abuse the `for` loop. – SLaks Jul 11 '13 at 02:34
  • 2
    If it's about pollution, you can always use a new scope, or the for loop. – chris Jul 11 '13 at 02:35
  • The most succinct (stupid) solution is probably `if ( int c = getc( stdin ) - 1 ) ; else ungetc( c + 1 );`. – Potatoswatter Jul 11 '13 at 03:24
  • Why this limitation of not lifting the variable definition and initialization out of the condition? – GManNickG Jul 11 '13 at 03:26
  • 2
    @GManNickG Fung shui? – Potatoswatter Jul 11 '13 at 03:29
  • @SLaks: That must be the biggest abuse I've heard of in a while, which is nevertheless such a cool idea that I'm almost tempted to _find a place_ to use it. `for(char c = getc(stdin); c == 0x01; ) {...}` -- not precisely principle-of-least-confusion, but awesome :-) Except... how does it exit, ever? – Damon Jul 11 '13 at 09:13
  • @Damon Fill in the last part: `for(char c = getc(stdin); c == 0x01; c = 42)` – Potatoswatter Jul 11 '13 at 13:44
  • @Potatoswatter: Ah right, of course :-) – Damon Jul 11 '13 at 14:02
  • possible duplicate of [How does one declare a variable inside an if () statement?](http://stackoverflow.com/questions/14620898/how-does-one-declare-a-variable-inside-an-if-statement) – Rapptz Jul 13 '13 at 00:08

3 Answers3

17

If it's the namespace pollution you are worrying about you can always define the if statement within a block:

{
    char c = getc(stdin);
    if(c == 0x01)
    {
        // ...
    }
}

So that c will only last until the end of the block is reached.

Shoe
  • 70,092
  • 30
  • 150
  • 251
  • 4
    Though didn't answer the question, this way of writting code is simple, clear and effective. Thank you. – Wesley Jul 11 '13 at 07:37
  • 3
    @qlb1234 Sometimes a question, as in your case, is effectively asking "how do I best write bad code". These questions aren't supposed to be answered with "this is how you write bad code", but with "don't write bad code, do this instead." :-) – Nikos C. Jul 11 '13 at 23:00
  • 2
    @NikosC.: The appeal of this answer is its simplicity, but I rarely find such constructs acceptable in a code review. When I see a standalone statement block like this, I always think the code should be in a separate function. – jxh Jul 12 '13 at 00:19
10

I didn't know how to create a variable and test its value with an if until after seeing some of the posted solutions. However, you could use switch. This would allow you to react to additional values (perhaps EOF):

switch (int c = getc(stdin)) {
case 0x01: ungetc(c, stdin); break;
case EOF:  // ...handle EOF
default:   break;
}

You could always place the if statement in an inlined function instead, and the code will look a little cleaner. If you really want the source code right at that location, but without creating a new scope around an if with a new variable, then perhaps a lambda would be acceptable to you.

[](int c){ if (c == 0x01) ungetc(c, stdin); }(getc(stdin));

Since you are only comparing against one valuem your particular problem does not require a variable at all, so you can simply do:

if (getc(stdin) == 0x01) {
    char c = 0x01;
    ungetc(c, stdin); //or bla...
}

If you are wanting to compare against a set of values, then the switch suggestion is the better option.

Jerry Coffin's solution looks appealing, but it really boils down to:

if (int c = (getc(stdin) == 0x01)) //...

This is probably not what you really wanted, as it does not generalize well if you want to compare to a value different from 0x01.

Potatoswatter's solution seems closer to what you want, but perhaps it would be nicer to pull the type out into a standalone class:

template <typename T>
class SetAndTest {
    const T test_;
    T set_;
public:
    SetAndTest (T s = T(), T t = T()) : set_(s), test_(t) {}
    operator bool () { return set_ == test_; }
    operator bool () const { return set_ == test_; }
    operator T & () { return set_; }
    operator T () const { return set_; }
};

//...
if (auto c = SetAndTest<int>(getc(stdin), 0x01)) {
    ungetc(c, stdin); //or bla...
}
jxh
  • 64,506
  • 7
  • 96
  • 165
8

You can define the variable inside the if statement just fine. For example, this should compile:

if (int ch = getchar())
    ;

The problem is that the type (e.g., int) must follow immediately after the opening parenthesis. The extra parenthesis you have is what's causing compilation to fail. So, if you really want to do this, you'll need to get a little clever and use something like this:

if (char ch = 0 || ((ch = getchar()) == 0x1))

This lets you get the creation and initialization of ch done, then after that part of the expression is complete, put in the parentheses around the ch=getchar() to override the precedence of assignment vs. comparison.

Note that && and || do short-circuit evaluation, so you need to be careful with your initialization. You can use either:

if (char ch = 0 || ...

...or:

if (char ch = 1 && ...

...but if you try to use if (ch = 1 || ... or if (ch = 0 && ..., the short-circuit evaluation will keep the right operand (the part you really care about) from being evaluated at all.

Now the caveat: while I'm reasonably certain this code fits the standard's requirements, and most (all?) current compilers will accept it, it's likely to cause most programmers reading the code some serious head-scratching figuring out what you've done, and why. I'd be extremely hesitant (at best) about using this "technique" in real code.

Edit: It's been pointed out that the result from this may be even more misleading than some initially expect, so I'll try to clarify the situation. What happens is that a value is read from input. That value is assigned to ch and compared to 0x1. So far so good. After that, the result of the comparison (converted to an integer, so either 0 or 1) will be assigned to ch. I believe it has sufficient sequence points that the result is defined behavior. But it's probably not what you, or anybody, want -- thus the advice that you probably don't want to use this, and the mention that it would probably leave most programmers scratching their heads, wondering what you were trying to do. In the very specific case of comparing to 0x1, the value of ch inside the if statement will be 1, but it's more or less a coincidence. If you were comparing to 0x2, the value of ch inside the if would still be 1, not 2.

Jerry Coffin
  • 437,173
  • 71
  • 570
  • 1,035
  • Couldn't you also use a comma? – sje397 Jul 11 '13 at 03:18
  • @sje397: Oddly enough, no -- not allowed in this case. – Jerry Coffin Jul 11 '13 at 03:19
  • What does the `0 ||` part do? It looks like `ch = true` will inevitably happen after `ch = getchar()`. – Potatoswatter Jul 11 '13 at 03:28
  • Wikipedia notes this as a use case for the comma operator in examples/condition here: http://en.wikipedia.org/wiki/Comma_operator. But they did once say the Great Wall was made of Star Wars action figures... – sje397 Jul 11 '13 at 03:30
  • 1
    @sje397: Every compiler I have rejects the code if you try to use a comma (and I believe the standard requires them to do so). – Jerry Coffin Jul 11 '13 at 03:35
  • @Potatoswatter: Oops -- need yet another set of parens to the right. Thanks for pointing it out. – Jerry Coffin Jul 11 '13 at 03:36
  • 1
    @JerryCoffin **-1**, no matter how many parens you put in there a boolean will always end up in `ch`. ie. `char ch = 0 || ((ch = getchar()) == 42)` will never have `ch == 42` inside the body of the *if-statement*. – Filip Roséen - refp Jul 11 '13 at 05:42
  • @refp: When all is said and done, yes -- but that's not what he asked about, is it? When the value is tested, it has the value that came from `getchar()`, which is what he seems to care about. – Jerry Coffin Jul 11 '13 at 06:07
  • @JerryCoffin and you think that makes sense? he wants to use `ch` (ie. the value of `getchar ()`) inside the *if-statement*, if he just wanted to test the value of `getchar ()` `getchar () == ...` would be sufficient. your answer implies that the value of `getchar ()` will be stored in `ch` and remain there inside the *if-statement*; something which isn't true. **quote from op**: *"To say it commonly, I want to use the variable(char c) both in the line of if and the body of if, but not outside the if."* – Filip Roséen - refp Jul 11 '13 at 06:12
  • @refp: I think you're inferring something that's not really implied anywhere. It doesn't seem to me to make much sense anyway -- he's specifically testing for equality to 1, so the only value it can have inside the `if` statement is one anyway (otherwise, nothing inside the `if` statement can execute). – Jerry Coffin Jul 11 '13 at 06:15
  • 2
    @JerryCoffin sure, but then you should also mention that what you are doing is equivalent to `if (getchar () == 0x1) { /* just use 'true' instead of ch in here */ }` – Filip Roséen - refp Jul 11 '13 at 06:16
  • @refp Yeah, that's true. The level of logical OR(`||`) is higher than the one of assignment(`=`). If written as above mentioned, `ch` will never equals to `getchar()`, and the body of `if` will never know the value of `getchar()`. If so, why would this question exsit? I've almost accept this answer for a mistake, but what he thinks is still clever. – Wesley Jul 11 '13 at 07:34
  • 2
    "This lets you get the creation and initialization of ch done, then [...]" actually the right hand side of the `||` is still part of the initialization expression. The syntax of the `if` condition is either an expression or a single declaration; there is no 'declaration followed by expression' and so your code is structured such that the expression is hidden inside a declaration's initializer. – bames53 Jul 11 '13 at 17:24
  • Additionally your code does not do the intended thing, as you say `ch` gets reassigned to 1 or 0, and does not keep the desired value that resulted from `getchar()`. – bames53 Jul 11 '13 at 17:27
  • N.B. this code is only well-defined because `char` does not have a non-trivial constructor, if you tried `if (NonTrivialInit p = 0 || ((p = something()) == x))` it would be undefined because `p` is assigned to before it has been initialized. Bad Things will happen. – Jonathan Wakely Jul 11 '13 at 22:24
  • @JerryCoffin I'm not sure I see the reason for the double assignment in `if(char ch = 0 || ((ch = getchar()) == 0x1))`. Wouldn't `if(char ch = 0 || (getchar() == 0x1))` be equivalent? And while we're at it ... `if(char ch = (getchar() == 0x1))` is still the same logic, only more readable [;-)](http://goo.gl/gOGev) – Fiktik Jul 11 '13 at 23:20