ядро языка — синтаксисcore language — syntax
Стандартизированные атрибуты вместо компиляторных расширений: [[nodiscard]] предупреждает об игнорировании возврата, [[maybe_unused]] подавляет предупреждение о неиспользуемой переменной, [[fallthrough]] документирует намеренное проваливание между case-метками.Standardized attributes instead of compiler extensions: [[nodiscard]] warns about an ignored return value, [[maybe_unused]] suppresses an unused-variable warning, [[fallthrough]] documents an intentional fallthrough between case labels.
Три независимых стандартных атрибута в общем синтаксисе [[...]], введённом ещё в C++11. [[nodiscard]] ставится на функцию или тип и предупреждает компилятором, если результат вызова проигнорирован (не сохранён, не использован). [[maybe_unused]] ставится на переменную, параметр функции или другую сущность и подавляет предупреждение "unused" для неё. [[fallthrough]] ставится отдельным стейтментом в конце блока case внутри switch и явно сообщает, что отсутствие break — осознанное решение, а не забытая строка.Three independent standard attributes sharing the [[...]] syntax introduced back in C++11. [[nodiscard]] is placed on a function or a type and makes the compiler warn if the call's result is ignored (not stored, not used). [[maybe_unused]] is placed on a variable, function parameter, or other entity and suppresses the "unused" warning for it. [[fallthrough]] is placed as its own statement at the end of a case block inside a switch and explicitly states that the missing break is a deliberate choice, not a forgotten line.
// GCC/Clang: непереносимое расширение// GCC/Clang: a non-portable extension
__attribute__((warn_unused_result))
bool try_lock();
// MSVC: другой синтаксис для той же идеи// MSVC: a different syntax for the same idea
_Check_return_ bool try_lock();
switch (v) {
case 1:
// falls through — компилятор не отличит от забытого break// falls through — the compiler can't tell it from a missing break
case 2:
handle();
}
[[nodiscard]] bool try_lock(); // работает одинаково везде// works identically everywhere
switch (v) {
case 1:
[[fallthrough]]; // явное намерение, никакой двусмысленности// explicit intent, no ambiguity
case 2:
handle();
}
Все три идеи давно существовали как расширения конкретных компиляторов — стандарт лишь унифицировал то, что уже де-факто требовалось разработчикам.All three ideas had long existed as extensions of specific compilers — the standard simply unified what developers already needed de facto.
Проекты, которые собирались несколькими компиляторами, были вынуждены оборачивать __attribute__((warn_unused_result)), _Check_return_ и аналоги в собственные макросы с #ifdef на каждый компилятор — стандартные атрибуты убирают необходимость в этом слое макросов полностью.Projects built with multiple compilers had to wrap __attribute__((warn_unused_result)), _Check_return_, and equivalents in their own macros with a #ifdef per compiler — standard attributes remove the need for that macro layer entirely.
Вызов std::async(...) без сохранения возвращаемого std::future в переменную приводит к тому, что деструктор временного future блокируется и ждёт завершения задачи прямо на месте вызова — то есть асинхронный вызов внезапно становится синхронным. Это настолько частая и незаметная ошибка, что стала одним из мотивирующих примеров для [[nodiscard]] в самой стандартной библиотеке.Calling std::async(...) without saving the returned std::future into a variable causes the temporary future's destructor to block and wait for the task to finish right at the call site — meaning an asynchronous call suddenly becomes synchronous. This mistake is common and inconspicuous enough that it became one of the motivating examples for [[nodiscard]] in the standard library itself.
Параметры, нужные только в debug-сборке, или переменные, используемые только внутри assert (который исчезает в release), давали предупреждение "unused parameter/variable" в release-сборке. Стандартного способа сказать "это нормально, не предупреждай" не было — использовались трюки вроде (void)variable; или приведение к void, работающие, но не документирующие намерение напрямую.Parameters needed only in a debug build, or variables used only inside assert (which disappears in release), triggered an "unused parameter/variable" warning in release builds. There was no standard way to say "this is fine, don't warn" — tricks like (void)variable; or a cast to void were used, which worked but didn't directly document the intent.
Все три атрибута — чисто диагностические указания компилятору, они не меняют кодогенерацию и не влияют на семантику программы, только на набор выдаваемых предупреждений.All three attributes are purely diagnostic hints to the compiler — they don't change code generation or affect the program's semantics, only the set of warnings emitted.
// [[nodiscard]] на функции — предупреждение при игнорировании возврата// [[nodiscard]] on a function — warns if the return value is ignored
[[nodiscard]] bool try_lock();
try_lock(); // предупреждение: результат try_lock проигнорирован// warning: the result of try_lock is discarded
// [[nodiscard]] на типе — распространяется на все функции, возвращающие этот тип// [[nodiscard]] on a type — applies to every function returning that type
[[nodiscard]] struct ErrorCode { int value; };
ErrorCode run();
run(); // тоже предупреждение — ErrorCode помечен [[nodiscard]]// also a warning — ErrorCode is marked [[nodiscard]]
// [[maybe_unused]] — подавляет предупреждение независимо от сборки// [[maybe_unused]] — suppresses the warning regardless of build type
void process([[maybe_unused]] int debug_only_param) {
assert(debug_only_param > 0); // в release assert пуст, но предупреждения нет// assert is empty in release, but no warning fires
}
// [[fallthrough]] — должен стоять непосредственно перед следующей case-меткой// [[fallthrough]] — must appear directly before the next case label
switch (level) {
case Level::high:
log_critical();
[[fallthrough]];
case Level::medium:
log_warning();
break;
case Level::low:
log_info();
}
Явно приведя вызов к void — (void)try_lock(); — можно легально проигнорировать [[nodiscard]]-результат без предупреждения: это одобренный стандартом способ сказать "я осознанно отбрасываю значение".Explicitly casting the call to void — (void)try_lock(); — legally ignores a [[nodiscard]] result without a warning: it's the standard-sanctioned way to say "I'm deliberately discarding this value."
Ничто не мешает всё равно проигнорировать результат — явное приведение к void легально отключает предупреждение, а некоторые компиляторы могут вообще не реализовывать предупреждение полностью или различаться в деталях (например, распространяется ли [[nodiscard]] на цепочку из нескольких вызовов). Полагаться на [[nodiscard]] как на способ гарантированно предотвратить ошибку в коде нельзя — это инструмент статического анализа, а не часть системы типов.Nothing prevents the result from still being ignored — an explicit cast to void legally suppresses the warning, and some compilers may not implement the warning fully or may differ in details (e.g. whether [[nodiscard]] propagates through a chain of several calls). Relying on [[nodiscard]] as a way to guarantee prevention of a coding mistake is wrong — it's a static-analysis tool, not part of the type system.
Это отдельный стейтмент (с точкой с запятой), а не комментарий и не декоратор на самой метке — если между [[fallthrough]]; и следующим case есть другой код (даже пустой стейтмент вроде лишней фигурной скобки не по месту), компилятор может не признать намерение и всё равно выдать предупреждение о непреднамеренном fallthrough.It's a standalone statement (with a semicolon), not a comment and not a decorator on the label itself — if there's other code between [[fallthrough]]; and the next case (even a stray empty statement or misplaced brace), the compiler may fail to recognize the intent and still emit the unintended-fallthrough warning.
Синтаксис [[...]] спроектирован так, что незнакомый атрибут — не ошибка компиляции, а просто отсутствие эффекта. Это удобно для переносимости (старый компилятор без поддержки C++17 просто проигнорирует атрибут вместо падения), но означает, что опечатка в имени атрибута (например, [[nodicard]]) не даст диагностики — код молча продолжит работать без предупреждений, которые должен был бы включить.The [[...]] syntax is designed so that an unrecognized attribute isn't a compile error, just a no-op. This is convenient for portability (an older compiler without C++17 support just ignores the attribute instead of failing), but it means a typo in the attribute's name (e.g. [[nodicard]]) produces no diagnostic — the code silently keeps working without the warnings it was supposed to enable.