C ++, объявление переменной в выражении if - PullRequest
102 голосов
/ 20 октября 2011

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

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' с x, установленным в false, тогда для объявления требуется скобка (так как оператор присваивания имеет более низкий приоритет, чем логическое ИЛИ), но поскольку скобка не может использоваться, она требует объявления x вне тела, утечка этого объявления в большую область, чем это желательно. Очевидно, этот пример тривиален, но более реалистичным будет случай, когда a и b являются функциями, возвращающими значения, которые должны быть проверены

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

Ответы [ 8 ]

101 голосов
/ 20 октября 2011

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

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

Оператор "&&" представляет собой логическое И короткое замыкание.Это означает, что если первая часть (1==0) окажется ложной, то вторая часть (bool a = false) не должна оцениваться, поскольку уже известно, что окончательный ответ будет ложным.Если (bool a = false) не оценивается, что делать с кодом, который использует a?Будем ли мы просто инициализировать переменную и оставить ее неопределенной?Будем ли мы инициализировать его по умолчанию?Что, если тип данных был классом, и это имело нежелательные побочные эффекты?Что, если вместо 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?

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

91 голосов
/ 20 октября 2011

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

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

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

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

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

с указанием одной декларации, без скобок или других украшений.

43 голосов
/ 25 мая 2017

Начиная с C ++ 17 то, что вы пытались сделать , наконец-то возможно :

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

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

20 голосов
/ 20 октября 2011

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

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

    if(bool x = a || b)
    {
        //...
    }
}//a and b are out of scope
18 голосов
/ 20 октября 2011

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

if (int a = Func1())
{
   if (int b = Func2())
   {
        // do stuff with a and b
   }
}
2 голосов
/ 06 июня 2015

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

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

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

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

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

    if (a && b)
    {
        std::cout << "a = " << a << std::endl;
        std::cout << "b = " << b << std::endl;
    }
}
1 голос
/ 25 октября 2011

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

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

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

0 голосов
/ 16 января 2015

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

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

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

...