С++, объявление переменной в выражении 'if'

Что здесь происходит?

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.
}

В разделе 6.4.3 в стандарте 2003 года указывается, как переменные, объявленные в условии утверждения выбора, имеют область действия, которая распространяется до конца подстановок, контролируемых условием. Но я не вижу, где он говорит что-то о том, что он не может помещать круглые скобки вокруг объявления, и не говорит ничего об одном объявлении за условие.

Это ограничение раздражает даже в тех случаях, когда требуется только одно объявление в условии. Рассмотрим это.

bool a = false, b = true;

if(bool x = a || b)
{

}

Если я хочу ввести область "if" -body с x, установленным в false, объявление требует скобки (поскольку оператор присваивания имеет более низкий приоритет, чем логический ИЛИ), но поскольку скобка не может использоваться, она требует объявления из x вне тела, утечка этого объявления в больший объем, чем это необходимо. Очевидно, что этот пример тривиален, но более реалистичным будет случай, когда a и b являются функциями, возвращающими значения, которые необходимо протестировать

Итак, что я хочу сделать несоответствующим стандарту, или мой компилятор просто разбивает мои яйца (VS2008)?

Ответ 1

Условие в выражении if или while может быть выражением или объявлением одной переменной (с инициализацией).

Второй и третий примеры не являются ни действительными выражениями, ни действительными объявлениями, поскольку объявление не может быть частью выражения. Хотя было бы полезно иметь возможность писать код, как ваш третий пример, это потребует значительного изменения синтаксиса языка.

Я не вижу, где он говорит что-то о том, что он не может помещать круглые скобки вокруг объявления, и не говорит ничего об одном объявлении за условие.

Спецификация синтаксиса в 6.4/1 дает следующее условие:

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

указав одно объявление, без круглых скобок или других украшений.

Ответ 2

Думаю, вы уже намекнули на этот вопрос. Что должен сделать компилятор с этим кодом?

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

"& &" оператор является логическим замыканием короткого замыкания. Это означает, что если первая часть (1==0) оказывается ложной, то вторая часть (bool a = false) не должна оцениваться, потому что уже известно, что окончательный ответ будет ложным. Если (bool a = false) не оценивается, то что делать с кодом позже, используя a? Не будем ли мы инициализировать переменную и оставить ее undefined? Мы бы инициализировали его по умолчанию? Что, если тип данных был классом, и у этого были нежелательные побочные эффекты? Что, если вместо bool вы использовали класс и у него не было конструктора по умолчанию, так что пользователь должен предоставить параметры - что мы будем делать тогда?

Вот еще один пример:

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?

Кажется, что ограничение, которое вы нашли, кажется вполне разумным - оно предотвращает подобные разногласия.

Ответ 3

Если вы хотите заключить переменные в более узкую область, вы всегда можете использовать дополнительные { }

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

    if(bool x = a || b)
    {
        //...
    }
}//a and b are out of scope

Ответ 4

Последний раздел уже работает, вам просто нужно написать его несколько иначе:

if (int a = Func1())
{
   if (int b = Func2())
   {
        // do stuff with a and b
   }
}

Ответ 5

В С++ 17, что вы пытались сделать наконец, возможно:

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

Обратите внимание на использование ; вместо , для разделения объявления и фактического состояния.

Ответ 6

Здесь уродливое обходное решение с использованием цикла (если обе переменные являются целыми):

#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;
}

Но это смутит других программистов, и это довольно плохой код, поэтому не рекомендуется.

Простой закрывающий блок {} (как уже рекомендовано) гораздо проще читать:

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

    if (a && b)
    {
        std::cout << "a = " << a << std::endl;
        std::cout << "b = " << b << std::endl;
    }
}

Ответ 7

Следует отметить, что выражения внутри большего if-блока

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

не обязательно гарантированно оцениваются слева направо. Один из довольно тонких ошибок, которые я получил в тот день, был связан с тем фактом, что компилятор фактически тестировал право налево, а не слева направо.

Ответ 8

С малой магией шаблонов вы можете как-то обойти проблему неспособности объявить несколько переменных:

#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;
}

(Обратите внимание, что это теряет оценку короткого замыкания.)