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.