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

114

Что тут происходит?

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)?

Нейтрино
источник
6
"Если я хочу войти в цикл с" <- в ваших примерах есть if. ifэто не цикл, это условный.
crashmstr
2
@crashmstr: true, но условия while такие же, как и для if.
Майк Сеймур,
2
Разве это нельзя сделать с помощью оператора запятой? Я имею ввиду if (int a = foo(), int b = bar(), a && b):? Если оператор запятой не перегружен, стандарт говорит, что выражения оцениваются слева направо, а результатом является последнее выражение. Он работает с forинициализацией циклов, почему не здесь?
Арчи
@Archie: Я только что попробовал, но не смог заставить его работать. Может, вы приведете рабочий пример?
Джеймс Джонстон
@JamesJohnston: Я только что пробовал, но, похоже, не работает. Эта идея просто пришла мне в голову, мне подсказали, как ifработает, и это кажется неправильным предположением.
Арчи

Ответы:

63

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

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

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

Fwyzard
источник
24
Ницца! Я всегда подозревал, что опередил свое время.
Neutrino
106

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

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?

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

Джеймс Джонстон
источник
1
Хорошая точка зрения. Возможно, вы захотите прямо упомянуть короткое замыкание, если OP или другие не знакомы с этим.
Крис Купер,
7
Я не думал об этом. Хотя в приведенном вами примере короткое замыкание предотвращает ввод области условного оператора, в этом случае часть выражения, которая объявляет, что переменная не обрабатывается, не является проблемой, поскольку ее область действия ограничена областью условного оператора. В каком случае не было бы лучше, если бы компилятор выдавал ошибку только в тех случаях, когда существует возможность входа в область условного оператора, когда часть выражения, в котором объявлена ​​переменная, не была обработана? В приведенных мною примерах этого не было.
Neutrino
@Neutrino На первый взгляд ваша идея немного напоминает SAT-задачу, которую не так-то просто решить, по крайней мере, в общем случае.
Кристиан Рау,
5
То, что вы объясняете обо всех проблемах, связанных с наличием нескольких объявлений переменных в условии if и тем фактом, что вы можете использовать их только ограниченным образом, заставляет меня задаться вопросом, почему вообще такое объявление было введено. Я никогда не чувствовал необходимости в таком синтаксисе, пока не увидел его в каком-нибудь примере кода. Мне этот синтаксис кажется довольно неуклюжим, и я считаю, что объявление переменной перед блоком if намного удобнее. Если вам действительно нужно ограничить область действия этой переменной, вы можете поместить дополнительный блок вокруг блока if. Я никогда не использовал этот синтаксис.
Джорджио
2
Лично я думаю, что было бы довольно элегантно иметь возможность ограничить область переменных, которые вы используете, точно областью блока оператора, который должен их использовать, без необходимости прибегать к уродливым мерам, таким как дополнительные вложенные скобки области видимости.
Neutrino
96

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

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

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

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

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

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

Майк Сеймур
источник
3
Есть ли для этого причина или предыстория?
Томаш Зато - Восстановите Монику
23

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

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

    if(bool x = a || b)
    {
        //...
    }
}//a and b are out of scope
crashmstr
источник
5
+1. Кроме того, я бы переместил объявление x в окружающий блок: почему у него должен быть особый статус относительно a и b?
Джорджио
1
Очевидно, но не убедительно: то же самое можно сказать и об обычных переменных цикла. (Конечно, потребность в ограниченной области видимости переменных гораздо чаще встречается в циклах.)
Питер - Восстановите Монику
18

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

if (int a = Func1())
{
   if (int b = Func2())
   {
        // do stuff with a and b
   }
}
Бо Перссон
источник
2

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

#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;
    }
}
основной6
источник
1

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

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

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

ГерцогБрымин
источник
5
Однако в настоящее время C99 требует, чтобы && и || оцениваются слева направо.
b0fh
2
Я думаю, что оценка аргументов справа налево была невозможна из-за логики короткого замыкания. Он всегда использовался для таких вещей, как проверка указателя и указателя в одном выражении, например if(p && p->str && *p->str) .... Справа налево было бы смертельно опасно и не хитро. С другими операторами - даже присваивание! - и аргументы вызова функции вы правы, но не с операторами короткого замыкания.
Питер - Восстановите Монику
1

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

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

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

БКС
источник
Я полагаю, он проигрывает (или ослабляет?)
Питер - Восстановите Монику
5
Я думаю, что целью OP была краткость кода, ясность и ремонтопригодность. Предложенное вами «решение» делает обратное.
Дженан