C ++, оголошення змінної у виразі "якщо"


113

Що тут відбувається?

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: правда, але умови для 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.
}

Зверніть увагу на використання ;замість того, ,щоб розділяти декларацію та фактичний стан.


23
Приємно! Я завжди підозрював, що я випередив свій час.
— Нейтрино

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
Гарна думка. Ви можете чітко згадати коротке замикання, якщо ОП або інші не знайомі з ним.
— Кріс Купер

7
Я не думав про це. Хоча в прикладі, який ви надали, коротке замикання перешкоджає введенню області умовного висловлювання, і в цьому випадку частина виразу, яка оголошує змінну, не обробляється, не є проблемою, оскільки її обсяг обмежений областю твердження. У такому разі не було б краще, якби компілятор вивів помилку лише в тих випадках, коли існує можливість введення області умовного оператора, коли частина виразу, яка оголосила змінну, не була оброблена? Що не було в прикладах, які я наводив.
— Нейтрино

@Neutrino На перший погляд ідея вам трохи схожа на проблему SAT, яку не так просто вирішити, принаймні в загальному випадку.
— Крістіан Рау

5
Те, що ви пояснюєте про всі проблеми з наявністю декількох оголошень змінної в умові if, і той факт, що ви можете використовувати їх лише обмеженим чином, змушує мене замислитися, чому на землі цей вид декларації був введений в першу чергу. Я ніколи не відчував потреби в такому синтаксисі, перш ніж бачив це в прикладі коду. Я вважаю цей синтаксис досить незграбним, і я думаю, що оголошення змінної перед блоком if набагато читабельніше. Якщо вам дійсно потрібно обмежити область застосування цієї змінної, ви можете поставити додатковий блок навколо блоку if. Я ніколи не використовував цей синтаксис.
— Джорджіо

2
Особисто я думаю, що було б досить елегантно мати можливість обмежувати область змінних, які ви використовуєте, саме сферою блоку операторів, який потребує їх використання, не вдаючись до некрасивих заходів, таких як додаткові вкладені діапазони шкали.
— Нейтрино

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

5
+1. Крім того, я б перемістив оголошення x на навколишній блок: чому він повинен мати спеціальний статус wrt 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;
    }
}

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
Я думаю, що наміром ОП була стислість, чіткість та ремонтопридатність коду. Ви запропонували "рішення" робить навпаки.
— Dženan
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.