диагностика и качество ошибок компиляцииdiagnostics and compiler error quality

= delete("reason")

Расширение синтаксиса удаления функций (C++11) необязательной строкой-объяснением: void old_api() = delete("use new_api() instead"); — компилятор обязан включить эту строку в диагностику при попытке вызова.An extension of function deletion syntax (C++11) with an optional explanatory string: void old_api() = delete("use new_api() instead"); — the compiler is required to surface this string in the diagnostic when the deleted function is called.

маленькое, чисто диагностическое добавлениеa small, purely diagnostic addition частый вопрос: меняет ли это разрешение перегрузкиcommon question: does this change overload resolution

Что этоWhat it is

P2573 расширяет существующий синтаксис = delete; необязательным строковым литералом: void old_api() = delete("use new_api() instead — removed in v3.0");. При попытке вызвать удалённую функцию компилятор обязан вывести эту строку как часть диагностики, а не только общее сообщение вида "call to deleted function".P2573 extends the existing = delete; syntax with an optional string literal: void old_api() = delete("use new_api() instead — removed in v3.0");. When code attempts to call the deleted function, the compiler is required to surface this string as part of the diagnostic, rather than only a generic "call to deleted function" message.

до C++26 — голая ошибка без контекстаbefore C++26 — a bare error with no context
void process(double) = delete;

process(3.14);
// error: use of deleted function 'void process(double)'// error: use of deleted function 'void process(double)'
// ...и всё — почему удалена, чем заменить, неизвестно// ...and that's it — why it was deleted, what to use instead, unknown
C++26 — причина прямо в диагностикеC++26 — the reason right in the diagnostic
void process(double) = delete("no implicit narrowing — cast explicitly");

process(3.14);
// error: use of deleted function 'void process(double)':// error: use of deleted function 'void process(double)':
//        no implicit narrowing — cast explicitly//        no implicit narrowing — cast explicitly

Зачем ввелиWhy it was introduced

01

Голый = delete; заставлял искать контекст вручнуюA bare = delete; forced you to hunt down context by hand

Без объяснения вызывающий код получал сухую ошибку компилятора без контекста — приходилось идти к объявлению, читать (если повезёт) комментарий над ним или искать в истории коммитов/документации, почему функция удалена и чем её заменить.Without an explanation, calling code got a dry compiler error with no context — you had to go find the declaration, read (if you were lucky) a comment above it, or search commit history/documentation to learn why the function was deleted and what to use instead.

02

Частая точка трения в эволюции библиотекA common friction point in library evolution

Намеренное устаревание или удаление перегрузки (например, чтобы предотвратить неявное опасное преобразование или заставить перейти на новый API) оставляло пользователей гадать, почему именно так и что теперь делать — жалоба на неинформативность диагностики = delete звучала давно и постоянно.Deliberately deprecating/removing an overload (e.g. to prevent a dangerous implicit conversion, or to force migration to a new API) left users guessing why, and what to do now — the complaint about = delete's unhelpful diagnostics was long-standing and recurring.

03

Самодокументирующаяся ошибка прямо в точке отказаA self-documenting error right at the point of failure

Необязательная строка, выведенная прямо в сообщении компилятора, превращает удаление в конкретное действие: пользователь узнаёт причину и — как правило — замену немедленно, вместо ухода в документацию за отдельным контекстом.The optional string, surfaced directly in the compiler's output, turns a deletion into actionable guidance: the user learns the reason and — typically — the replacement immediately, instead of leaving the editor to hunt for context elsewhere.

Как это работает под капотомHow it works under the hood

Синтаксис — это = delete(string-literal); вместо голого = delete;, применимый к любой удаляемой функции: обычной перегрузке, конструктору, оператору, шаблонной специализации. Строка не участвует ни в разрешении перегрузки, ни в какой-либо семантике программы — это чисто диагностический текст.The syntax is = delete(string-literal); instead of a bare = delete;, applicable to any deletable function: an ordinary overload, a constructor, an operator, a template specialization. The string plays no role in overload resolution or any program semantics — it's purely diagnostic text.

void process(int) {}
void process(double) = delete("no implicit narrowing — cast explicitly");

class Widget {
public:
    Widget(const Widget&) = delete("Widget owns a unique resource — use std::move");
    Widget(Widget&&) = default;
};

// комбинация с [[deprecated]] иллюстративна, но избыточна:// combining with [[deprecated]] is illustrative but redundant:
// = delete уже полностью запрещает вызов, deprecated ничего не добавляет// = delete already fully prevents the call, deprecated adds nothing
[[deprecated]] void legacy_call() = delete("removed in v3.0, use new_call() instead");

Примеры из реального кодаReal-world examples

типичные сценарии использованияtypical usage scenarios
01

Запрет опасного неявного преобразованияPreventing a dangerous implicit conversion

Перегрузка, удалённая специально для того, чтобы перехватить нежелательное неявное числовое сужение или преобразование указателя в bool, с объяснением опасности прямо в тексте ошибки.An overload deleted specifically to intercept an unwanted implicit numeric narrowing or pointer-to-bool conversion, with the danger explained right in the error text.

void set_flag(bool) {}
void set_flag(void*) = delete("pointer-to-bool is almost always a bug — compare to nullptr explicitly");
02

Move-only RAII-класс с объяснённым удалением копированияA move-only RAII class with an explained deleted copy

Класс, владеющий уникальным ресурсом, объясняет прямо в ошибке компиляции, почему копирование запрещено и что использовать вместо него — та же логика владения, что и в std::unique_ptr.A class owning a unique resource explains directly in the compile error why copying is forbidden and what to use instead — the same ownership logic as std::unique_ptr.

class FileHandle {
public:
    FileHandle(const FileHandle&) = delete("FileHandle owns a unique resource — use std::move");
    FileHandle& operator=(const FileHandle&) = delete("FileHandle owns a unique resource — use std::move");
    FileHandle(FileHandle&&) noexcept = default;
};
03

Удаление legacy-функции при миграции APIDeleting a legacy function during an API migration

Старая свободная функция удаляется с прямым указанием имени функции-замены, вместо того чтобы оставлять её работающей до неопределённого момента "когда-нибудь уберём".An old free function is deleted with a direct pointer to the replacement function's name, instead of leaving it working until some vague "we'll remove it eventually" point.

void connect_legacy(const char*) = delete("removed in v3.0, use connect(std::string_view, Options) instead");
04

Удаление опасной специализации шаблонаDeleting a dangerous template specialization

Специализация шаблона для конкретного типа удаляется, чтобы предотвратить неправильное использование — со ссылкой на уже известную проблему этого типа, описанную в другом месте базы знаний.A template specialization for a specific type is deleted to prevent misuse — pointing to a known issue with that type documented elsewhere in this knowledge base.

template<>
void serialize<std::vector<bool>>(const std::vector<bool>&) =
    delete("std::vector<bool> is not a real container — see gotcha in unordered-containers.html");

ГраблиGotchas

Строка чисто диагностическая — на семантику не влияетThe string is purely diagnostic — no effect on semantics

Причина не влияет ни на разрешение перегрузки, ни на какую-либо семантику программы — поведение идентично обычному = delete; во всём, кроме текста ошибки компилятора. Это не меняет, что компилируется, а только то, насколько понятно это падает.The reason has zero effect on overload resolution or any program semantics — behavior is identical to a plain = delete; in everything except the compiler's error text. It doesn't change what compiles, only how clearly it fails.

Формат вывода строки не унифицирован между компиляторамиThe string's presentation isn't unified across compilers

Разные компиляторы могут по-разному форматировать/встраивать строку-причину в диагностику — это implementation-defined представление, а не гарантированно одинаковый формат между GCC/Clang/MSVC. Не стоит рассчитывать на идентичную формулировку ошибки во всех тулчейнах.Different compilers may format/surface the reason string differently in their diagnostics — this is implementation-defined presentation, not a guaranteed identical format across GCC/Clang/MSVC. Don't assume identical error message wording across toolchains.

Удалённая функция всё ещё участвует в разрешении перегрузкиA deleted function still participates in overload resolution

Удалённая функция (с причиной или без) по-прежнему может быть ВЫБРАНА как лучшее совпадение (что и вызывает ошибку удаления) — она не невидима для компилятора так, будто объявления вовсе не существует. В этом и смысл: поймать конкретную ошибку и объяснить её, а не сделать вид, что перегрузки нет — распространённое заблуждение в обратную сторону.A deleted function (with or without a reason) can still be SELECTED as the best match (which is what triggers the deletion error) — it isn't invisible to the compiler the way removing the declaration entirely would be. That's the whole point: to catch the specific mistake and explain it, not to make the overload disappear — a common misunderstanding in the opposite direction.