152

What's going on here?

if(int a = Func1())
{
    // Works.
}

if((int a = Func1()))
{
    // Fails to compile.
}

if((int a = Func1())
    && (int b = Func2()))
)
{
    // Do stuff with a and b.
    // This is what I'd really like to be able to do.
}

Section 6.4.3 in the 2003 standard explains how variables declared in a selection statement condition have scope that extends to the end of the substatements controlled by the condition. But I don't see where it says anything about not being able to put parenthesis around the declaration, nor does it say anything about only one declaration per condition.

This limitation is annoying even in cases where only one declaration in the condition is required. Consider this.

bool a = false, b = true;

if(bool x = a || b)
{

}

If I want to enter the 'if'-body scope with x set to false then the declaration needs parenthesis (since the assignment operator has lower precedence than the logical OR), but since parenthesis can't be used it requires declaration of x outside the body, leaking that declaration to a greater scope than is desired. Obviously this example is trivial but a more realistic case would be one where a and b are functions returning values that need to be tested

So is what I want to do non-conformant to the standard, or is my compiler just busting my balls (VS2008)?

Scheff's Cat
  • 19,528
  • 6
  • 28
  • 56
Neutrino
  • 8,496
  • 4
  • 57
  • 83
  • 10
    "If I want to enter the loop with" <-- your examples have `if`. `if` is not a loop, it is a conditional. – crashmstr Oct 20 '11 at 13:48
  • 3
    @crashmstr: true, but the conditions for `while` are the same as for `if`. – Mike Seymour Oct 20 '11 at 14:08
  • 2
    Can't this be done with the comma operator? I mean: `if (int a = foo(), int b = bar(), a && b)`? If comma operator is not overloaded, the standard says that the expressions are evaluated from left to right, and the result value is the last expression. It works with `for` loops initialization, why not here? – Archie Oct 20 '11 at 14:46
  • 1
    @Archie: I just tried this, I couldn't get it to work. Maybe you can provide a working example? – James Johnston Oct 20 '11 at 15:10
  • @JamesJohnston: I just also have tried, and it doesn't seem to work. That idea just came from the top of my head, I was suggested by how `if` works, and it seems to be wrong assumption. – Archie Oct 20 '11 at 15:52

8 Answers8

138

As of C++17 what you were trying to do is finally possible:

if (int a = Func1(), b = Func2(); a && b)
{
    // Do stuff with a and b.
}

Note the use of ; of instead of , to separate the declaration and the actual condition.

fwyzard
  • 2,364
  • 1
  • 21
  • 19
118

I think you already hinted at the issue. What should the compiler do with this code?

if (!((1 == 0) && (bool a = false))) {
    // what is "a" initialized to?

The "&&" operator is a short-circuit logical AND. That means that if the first part (1==0) turns out to be false, then the second part (bool a = false) should be not be evaluated because it is already known that the final answer will be false. If (bool a = false) isn't evaluated, then what to do with code later on that uses a? Would we just not initialize the variable and leave it undefined? Would we initialize it to the default? What if the data type was a class and doing this had undesirable side effects? What if instead of bool you used a class and it had no default constructor such that the user must provide parameters - what do we do then?

Here's another example:

class Test {
public:
    // note that no default constructor is provided and user MUST
    // provide some value for parameter "p"
    Test(int p);
}

if (!((1 == 0) && (Test a = Test(5)))) {
    // now what do we do?!  what is "a" set to?

Seems like the limitation you have found seems perfectly reasonable - it prevents these kinds of ambiguities from happening.

James Johnston
  • 9,264
  • 9
  • 48
  • 76
  • 2
    Good point. You might want to mention short-circuiting explicitly, in case the OP or others are not familiar with it. – Chris Cooper Oct 20 '11 at 13:52
  • 7
    I hadn't thought of that. Although in the example you provided the short circuiting prevents the conditional statement scope from being entered, in which case the part of the expression that declares the varible not being processed is not an issue, since its scope is limited to that of the conditional statement. In which case wouldn't it be better if the compiler raised an error only in those cases where there is a possibilty of the conditional statement scope being entered when a part of the expression that declared a variable was not processed? Which was not the case in the examples I gave. – Neutrino Oct 20 '11 at 14:35
  • @Neutrino At first sight you idea sounds a bit like the SAT-problem, which is not that easy to solve, at least in the general case. – Christian Rau Oct 20 '11 at 14:37
  • @Neutrino: Edited my examples so that the expression evaluates to true instead of false. I think my points still stand. While you are right and the compiler could theoretically do a static analysis to allow variable declarations in these specific cases, that adds a lot of complexity to the language with little benefit (how should the static analysis be done? this has to be defined for portability). The proposed feature starts at -100 points and probably doesn't have enough benefits to implement it: http://blogs.msdn.com/b/ericgu/archive/2004/01/12/57985.aspx – James Johnston Oct 20 '11 at 14:57
  • 5
    What you explain about all the problems with having multiple variable declarations in the if condition and the fact that you can only use them in a restricted way makes me wonder why on earth this kind of declaration was introduced in the first place. I had never _ever_ felt the need for such a syntax before I saw it in some code example. I find this syntax rather clumsy and I think that declaring the variable before the if block is much more readable. If you really need to restrict the scope of that variable you can put an extra block around the if block. I have never used this syntax. – Giorgio Oct 20 '11 at 15:53
  • 2
    Personally I think it would be rather elegant to be able to able to restrict the scope of variables you are using to exactly the scope of the statement block that needs to use them, without having to resort to ugly measures like extra nested scoping braces. – Neutrino Oct 20 '11 at 16:25
  • I hate when someone gives a reason like this. We could very easily define the rules - this is never a problem. The real one is that I doubt someone will later add them into the existing language. And also will this syntax improve anything in it? Think about those things instead of stating stupid things - NOTHING is impossible and everything can be implemented with the proper motivation. – AnArrayOfFunctions Apr 04 '15 at 16:53
  • People really like to believe that the current system is perfect and that there is a logical reason for it. You just give them this reason and they liked - no-matter how ridiculous actually is. – AnArrayOfFunctions Apr 04 '15 at 16:58
102

The condition in an if or while statement can be either an expression, or a single variable declaration (with initialisation).

Your second and third examples are neither valid expressions, nor valid declarations, since a declaration can't form part of an expression. While it would be useful to be able to write code like your third example, it would require a significant change to the language syntax.

I don't see where it says anything about not being able to put parenthesis around the declaration, nor does it say anything about only one declaration per condition.

The syntax specification in 6.4/1 gives the following for the condition:

condition:
    expression
    type-specifier-seq declarator = assignment-expression

specifying a single declaration, with no parentheses or other adornments.

Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
23

If you want to enclose variables in a narrower scope, you can always use additional { }

//just use { and }
{
    bool a = false, b = true;

    if(bool x = a || b)
    {
        //...
    }
}//a and b are out of scope
crashmstr
  • 28,043
  • 9
  • 61
  • 79
  • 5
    +1. Additionally, I would move the declaration of x to the surrounding block: why should it have a special status wrt a and b? – Giorgio Oct 20 '11 at 16:05
  • 1
    Obvious, but not convincing: The same could be said for ordinary loop variables. (Granted, the need for limited variable scope is much more common in loops.) – Peter - Reinstate Monica Mar 17 '17 at 13:06
18

The last section already works, you just have to write it slightly different:

if (int a = Func1())
{
   if (int b = Func2())
   {
        // do stuff with a and b
   }
}
Bo Persson
  • 90,663
  • 31
  • 146
  • 203
2

Here's an ugly workaround using a loop (if both variables are integers):

#include <iostream>

int func1()
{
    return 4;
}

int func2()
{
    return 23;
}

int main()
{
    for (int a = func1(), b = func2(), i = 0;
        i == 0 && a && b; i++)
    {
        std::cout << "a = " << a << std::endl;
        std::cout << "b = " << b << std::endl;
    }

    return 0;
}

But this will confuse other programmers and it's rather bad code, so not recommended.

A simple enclosing {} block (as already recommended) is much easier to read:

{
    int a = func1();
    int b = func2();

    if (a && b)
    {
        std::cout << "a = " << a << std::endl;
        std::cout << "b = " << b << std::endl;
    }
}
basic6
  • 3,643
  • 3
  • 42
  • 47
1

One thing to note, also is that the expressions inside the larger if-block

if (!((1 == 0) && (bool a = false)))

are not necessarily guaranteed to be evaluated in a left-to-right fashion. One rather subtle bug that I had back in the day had to do with the fact that the compiler was actually testing right-to-left instead of left-to-right.

DukeBrymin
  • 351
  • 3
  • 15
  • 5
    Nowadays, however, C99 mandates that && and || are evaluated left-to-right. – b0fh Jul 03 '13 at 20:25
  • 3
    I think right-to-left evaluation of the arguments was never possible due to short-circuit logic. It was always used for things like a test for pointer and pointee in a single expression, like `if(p && p->str && *p->str) ...`. Right-to left would have been deadly and not subtle. With other operators -- even assignment! -- and function call arguments you are right, but not with the short-circuit operators. – Peter - Reinstate Monica Mar 17 '17 at 13:11
0

With a little template magic you can kind of sort of get around the problem of not being able to declare multiple variables:

#include <stdio.h>

template <class LHS, class RHS>
struct And_t {
  LHS lhs;
  RHS rhs;

  operator bool () {
    bool b_lhs(lhs);
    bool b_rhs(rhs);
    return b_lhs && b_rhs;
  }
};
template <class LHS, class RHS> 
And_t<LHS, RHS> And(const LHS& lhs, const RHS& rhs) { return {lhs, rhs}; }

template <class LHS, class RHS>
struct Or_t {
LHS lhs;
RHS rhs;

  operator bool () {
    bool b_lhs(lhs);
    bool b_rhs(rhs);
    return b_lhs || b_rhs;
  }
};
template <class LHS, class RHS> 
Or_t<LHS, RHS> Or(const LHS& lhs, const RHS& rhs) { return {lhs, rhs}; }

int main() {
  if (auto i = And(1, Or(0, 3))) {
    printf("%d %d %d\n", i.lhs, i.rhs.lhs, i.rhs.rhs);
  }
  return 0;
}

(Note, this looses the short circuit evaluation.)

BCS
  • 75,627
  • 68
  • 187
  • 294