Визначення змінної в частині умови оператора if?


76

Я був просто вражений тим, що це дозволено:

if( int* x = new int( 20 ) )
{
    std::cout << *x << "!\n";
    // delete x;
}
else
{
    std::cout << *x << "!!!\n";
    // delete x;
}
// std:cout << *x; // error - x is not defined in this scope

Отже, чи дозволено це стандартом, чи це просто розширення компілятора?


PS Оскільки з цього приводу було декілька коментарів, будь ласка, проігноруйте, що цей приклад є "поганим" або небезпечним. Я знаю що. Це лише перше, що мені спало на думку, як приклад.


1
Гарне запитання, і, здається, у вас на думці досвід C - +1 за це. :)

@ H2CO3 - дякую :) Але як щодо C? Ви маєте на увазі, що це заборонено там, і це, можливо, змусило мене думати, що це не дозволено в C ++?
Кирил Кіров

@ H2CO3 - Ха-ха, дякую :) Я нічого не мав на увазі, мені навіть було цікаво, чи це те саме в C :))
Кирил Кіров

2
оскільки це лише стандарт у C99 ...

Необхідна new (std::nothrow)версія - інакше ваш приклад ніколи не перейде elseчерез std::bad_allocпомилку розподілу.
PiotrNycz

Відповіді:


82

Це дозволено специфікацією, оскільки C ++ 98.

З розділу 6.4 "Заяви про відбір":

Ім'я, введене декларацією в умові (або введеною типом-специфікатором-seq, або декларатором умови), перебуває в області дії від моменту декларації до кінця підзакладів, контрольованих умовою.

Наступний приклад з того самого розділу:

if (int x = f()) {
    int x;    // ill-formed, redeclaration of x
}
else {
    int x;    // ill-formed, redeclaration of x
}

21
Боже! Я щойно зрозумів (завдяки цій цитаті), що ім’я також було в межах else. Якось я завжди думав, що він буде просто доступний у ifрозділі ...
Матьє М.,

19

Насправді це не відповідь (але коментарі не дуже підходять для зразків коду), більше причин, чому це неймовірно зручно:

if (int* x = f()) {
    std::cout << *x << "\n";
}

Всякий раз, коли API повертає тип "option" (який також має булеве перетворення), цей тип конструкції може бути використаний таким чином, що змінна доступна лише в контексті, де розумно використовувати її значення. Це справді потужна ідіома.


Я ніколи не бачив цього з точки зору типів опцій і завжди сильно пропустив би їх під час кодування на C ++. Дякую, що змінили моє життя на краще!
hugomg

@Matthieu M. Я не зовсім розумію вашу думку щодо типу опціону. Не могли б ви розширити згадану ідіому чи надати якесь додаткове посилання. Звучить досить цікаво. На даний момент я використовую boost :: optional для таких речей. Ви маєте на увазі використовувати звичайний вказівник, як необов’язковий тип, і мати обмежений обсяг для нього, чи є щось більше за ним
Мартін,

1
@Martin: під типом опції я маю на увазі типи, які, як вказівники (або boost::optional) мають значення "сторожове", щоб сигналізувати про відсутність реального значення. Якщо вони також забезпечують перетворення на bool, вартість якого залежить виключно від наявності / відсутності реальної вартості, що зберігається у типі, тоді ідіома працює. Наприклад: if (boost::optional<int> x = f()) { std::cout << *x << '\n'; }також працює.
Matthieu M.

18

Це стандарт, навіть у старій версії мови C ++ 98:

введіть тут опис зображення


7

Визначення змінної в умовній частині while, ifі switchзаяви є стандартними. Відповідний пункт - 6.4 [stmt.select], параграф 1, який визначає синтаксис умови.

До речі, ваше використання є безглуздим: якщо newне вдається, це видає std::bad_allocвиняток.


3
Це був лише приклад. Я знаю, це погано. Це було лише перше, що мені спало на думку.
Кирил Кіров,

Чи оптимально визначати змінну в частині '' '' while '' ''?
Джулен

2

Ось приклад, що демонструє нетипове використання змінної, оголошеної в умові if .

Тип змінної - int &це як конвертована булева, так і придатна для використання у тодішніх та інших гілках.

#include <string>
#include <map>
#include <vector>
using namespace std;

vector<string> names {"john", "john", "jack", "john", "jack"};
names.push_back("bill"); // without this push_back, my g++ generated exe fails :-(
map<string, int> ages;
int babies = 0;
for (const auto & name : names) {
    if (int & age = ages[name]) {
        cout << name << " is already " << age++ << " year-old" << endl;
    } else {
        cout << name << " was just born as baby #" << ++babies << endl;
        ++age;
    }
}

вихід є

john was just born as baby #1
john is already 1 year-old
jack was just born as baby #2
john is already 2 year-old
jack is already 1 year-old
bill was just born as baby #3

На жаль, змінна в умові може бути оголошена лише із синтаксисом декларації '='.

Це виключає інші, можливо, корисні випадки типів з явним конструктором.

Наприклад, наступний приклад використання std::ifstreamкомпіляції не буде ...

if (std::ifstream is ("c:/tmp/input1.txt")) { // won't compile!
    std::cout << "true: " << is.rdbuf();
} else {
    is.open("c:/tmp/input2.txt");
    std::cout << "false: " << is.rdbuf();
}

Відредаговано в січні 2019 року ... тепер ви можете наслідувати те, що, як я пояснив, неможливо зробити ...

Це працює для рухомих класів, таких як ifstream в C ++ 11, і навіть для непридатних для копіювання класів, починаючи з C ++ 17 з копією elision.

Відредаговано в травні 2019: використовуйте авто для полегшення багатослівності

{
    if (auto is = std::ifstream ("missing.txt")) { // ok now !
        std::cout << "true: " << is.rdbuf();
    } else {
        is.open("main.cpp");
        std::cout << "false: " << is.rdbuf();
    }
}
struct NoCpy {
    int i;
    int j;
    NoCpy(int ii = 0, int jj = 0) : i (ii), j (jj) {}
    NoCpy(NoCpy&) = delete;
    NoCpy(NoCpy&&) = delete;
    operator bool() const {return i == j;}
    friend std::ostream & operator << (std::ostream & os, const NoCpy & x) {
        return os << "(" << x.i << ", " << x.j << ")";
    }
};
{
    auto x = NoCpy(); // ok compiles
    // auto y = x; // does not compile
    if (auto nocpy = NoCpy (7, 8)) {
        std::cout << "true: " << nocpy << std::endl;
    } else {
        std::cout << "false: " << nocpy << std::endl;
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.