ядро языка — синтаксисcore language — syntax
Переменная, объявленная прямо в условии if/switch, видна в теле и во всех ветках, но не утекает за пределы конструкции — if (auto it = m.find(key); it != m.end()).A variable declared right in the condition of if/switch is visible in the body and every branch, but doesn't leak past the construct — if (auto it = m.find(key); it != m.end()).
C++17 разрешает вставить инициализирующий стейтмент перед условием в if и перед селектором в switch: if (init; cond) и switch (init; cond). Переменная, объявленная в init, попадает в собственный скоуп, который охватывает и cond, и тело if, и ветку else (или все case-метки в switch) — но перестаёт существовать сразу после конструкции целиком.C++17 allows inserting an init-statement before the condition in if and before the selector in switch: if (init; cond) and switch (init; cond). A variable declared in init gets a scope of its own that covers cond, the if body, and the else branch (or all case labels in a switch) — but stops existing right after the whole construct.
auto it = m.find("key"); // остаётся видна после if// stays visible after the if
if (it != m.end()) {
use(it);
}
// it всё ещё в скоупе тут — мусорит пространство имён// it is still in scope here — clutters the namespace
do_something_unrelated(it); // случайно доступно, хотя не должно быть// accidentally accessible, though it shouldn't be
if (auto it = m.find("key"); it != m.end()) {
use(it);
}
// it здесь уже не существует — компилятор не даст обратиться// it no longer exists here — the compiler won't allow access
Проблема была не в отсутствии возможности — { auto it = ...; if (...) {...} } работал всегда. Проблема в эргономике и в самой частой ошибке C++-кода: скоупе, который живёт дольше, чем нужно.The problem wasn't a missing capability — { auto it = ...; if (...) {...} } always worked. The problem was ergonomics, and one of the most common mistakes in C++ code: a scope that lives longer than it should.
Переменная вроде it, нужная только для одной проверки, объявленная строкой раньше, оставалась доступна во всём остальном теле функции — читатель кода не мог по объявлению понять, что она предназначена только для одной проверки и её нельзя переиспользовать дальше.A variable like it, needed only for one check but declared a line earlier, stayed accessible throughout the rest of the function body — a reader couldn't tell from the declaration that it was meant for a single check and shouldn't be reused later.
Паттерн «получить значение, проверить успешность, использовать значение» — find, try_lock, парсинг — встречается постоянно. До C++17 он всегда требовал минимум двух строк и отдельного объявления переменной вне if, что провоцировало забыть про очистку/переиспользование этой переменной ниже по коду.The "get a value, check success, use the value" pattern — find, try_lock, parsing — comes up constantly. Before C++17 it always required at least two lines and a separate variable declaration outside the if, which invited forgetting to clean up or reuse that variable further down.
{ } ради ограничения времени жизниAn extra { } block was needed just to bound the lifetimeЕдинственный способ по-настоящему ограничить видимость переменной старым синтаксисом — обернуть весь код в дополнительные фигурные скобки, что добавляло уровень вложенности ради чисто косметической цели.The only way to genuinely limit a variable's visibility with the old syntax was to wrap everything in an extra pair of braces, which added a level of nesting for a purely cosmetic goal.
Формально if (init; cond) body1 else body2 — это просто синтаксический сахар, эквивалентный оборачиванию всей конструкции в блок:Formally, if (init; cond) body1 else body2 is just syntactic sugar equivalent to wrapping the whole construct in a block:
if (auto it = m.find(key); it != m.end()) {
use(it);
} else {
log_missing(it); // it виден и здесь, в else// it is visible here too, in else
}
// концептуально эквивалентно:// conceptually equivalent to:
{
auto it = m.find(key);
if (it != m.end()) {
use(it);
} else {
log_missing(it);
}
} // it выходит из скоупа здесь// it goes out of scope here
Для switch действует та же схема — init исполняется один раз перед вычислением селектора, и объявленная в нём переменная видна во всех case-метках:The same scheme applies to switch — init executes once before the selector is evaluated, and any variable declared in it is visible across all case labels:
switch (auto code = compute_status(); code) {
case Status::ok:
handle_ok(code);
break;
case Status::retry:
handle_retry(code);
break;
default:
handle_error(code);
}
// code вышел из скоупа сразу после switch// code went out of scope right after the switch
Можно также сочетать init со structured binding, что даёт особенно компактную идиому для контейнеров с find:The init can also be combined with a structured binding, giving a particularly compact idiom for containers with find:
if (auto [it, inserted] = m.insert({key, value}); inserted) {
log_new_entry(it);
}
Легко подумать, что init относится только к телу if, но на самом деле его скоуп охватывает всю конструкцию целиком, включая else. Это обычно удобно (см. пример с log_missing выше), но при быстром чтении кода можно не заметить, что переменная из «успешной» ветки на самом деле доступна и в «неуспешной».It's easy to assume init belongs only to the if body, but its scope actually covers the entire construct, including else. This is usually convenient (see the log_missing example above), but a quick read of the code can miss that a variable from the "success" branch is in fact available in the "failure" branch too.
Частый вопрос — можно ли использовать переменную из init в качестве значения case-метки. Нет: метки case в любом случае обязаны быть константными выражениями времени компиляции, и это требование ничего не меняет с появлением switch-init — init лишь готовит значение для селектора, а не для меток.A common question is whether a variable from init can be used as a case label's value. It can't: case labels are always required to be compile-time constant expressions, and that requirement is unchanged by switch-init — init only prepares the value for the selector, not for the labels.
Если значение нужно и после if/switch, синтаксис с init не подходит — переменная всё равно исчезнет в конце конструкции. В этом случае по-прежнему верно объявлять переменную обычным способом до if.If the value is still needed after the if/switch, the init syntax doesn't fit — the variable will still disappear at the end of the construct. In that case it's still correct to declare the variable the ordinary way before the if.