ядро языкаcore language

auto(x) / auto{x}

Явное decay-копирование выражения — создать независимую копию того же типа, что вывел бы auto, без промежуточной именованной переменной.An explicit decay-copy of an expression — creating an independent copy of the type auto would have deduced, without an intermediate named variable.

небольшая, но полезная в generic-коде фичаa small but useful feature for generic code частый вопрос: чем отличается от static_castcommon question: how it differs from static_cast

Что этоWhat it is

auto(x) и auto{x} создают временный объект, тип которого выводится по тем же правилам, что и для переменной, объявленной как auto (см. cpp11/auto.html) — то есть без ссылок и без верхнеуровневых cv-квалификаторов, даже если исходное выражение x было ссылкой на const. Результат — независимая, изменяемая копия значения, полученная прямо в месте использования выражения, без объявления промежуточной переменной.auto(x) and auto{x} create a temporary object whose type is deduced by the same rules as for a variable declared with auto (see cpp11/auto.html) — that is, with no references and no top-level cv-qualifiers, even if the original expression x was a reference to const. The result is an independent, mutable copy of the value, obtained right at the point of use, with no intermediate variable declared.

до C++23 — именованная переменная или decay_tbefore C++23 — a named variable or decay_t
const std::string& get_ref();

void use_copy() {
    auto copy = get_ref();  // требуется имя переменной, отдельная строка// needs a variable name, a separate line
    process(copy);
    // либо многословно прямо в выражении:// or, verbosely, right in the expression:
    process(std::decay_t<decltype(get_ref())>(get_ref()));
}
C++23 — auto(x) прямо в выраженииC++23 — auto(x) right in the expression
const std::string& get_ref();

void use_copy() {
    process(auto(get_ref()));  // decay-копия прямо на месте, без имени// a decay-copy right there, with no name
}

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

Идиома "форсировать decay-копию значения" встречается достаточно часто, чтобы заслужить собственный лаконичный синтаксис — до C++23 все доступные способы были неудобны хотя бы в одном отношении:The "force a decay-copy of a value" idiom comes up often enough to deserve its own concise syntax — before C++23, every available approach was inconvenient in at least one respect:

01

Именованная переменная занимает отдельную строку и требует имениA named variable takes a separate line and needs a name

auto copy = x; — самый очевидный способ, но он требует придумать имя для переменной, которая часто нужна ровно один раз, сразу передаётся куда-то дальше, и не несёт самостоятельной смысловой роли — лишняя строка кода и лишняя сущность в области видимости ради одной операции копирования.auto copy = x; is the most obvious approach, but it requires inventing a name for a variable that's often used exactly once, gets immediately passed onward, and carries no independent semantic role — an extra line of code and an extra entity in scope for the sake of a single copy operation.

02

std::decay_t<decltype(x)>(x) — многословно для частой операцииstd::decay_t<decltype(x)>(x) is verbose for a frequent operation

Конструкция std::decay_t<decltype(x)>(x) достигает того же результата без именованной переменной, но требует повторить выражение x дважды (один раз внутри decltype, второй раз как аргумент конструктора) и явно подключить <type_traits> — многословно и подвержено ошибкам при рефакторинге, если x — сложное выражение, которое меняется в одном месте, но забывается в другом.The std::decay_t<decltype(x)>(x) construct achieves the same result without a named variable, but requires repeating the expression x twice (once inside decltype, once as the constructor argument) and explicitly including <type_traits> — verbose and error-prone during refactoring if x is a complex expression that gets changed in one place but forgotten in the other.

03

Особенно полезно в generic-коде и цепочках ranges/лямбдEspecially useful in generic code and ranges/lambda chains

В шаблонном коде тип x часто неизвестен явно — писать std::decay_t<decltype(x)> руками для каждого такого места избыточно, когда auto уже умеет выводить нужный тип. В композиции с ranges-адаптерами и лямбдами часто нужно "форсировать копию" элемента прямо внутри выражения (например, чтобы гарантированно захватить значение, а не ссылку на временный прокси-объект range), не вынося отдельную переменную наружу.In template code the type of x is often not explicitly known — writing std::decay_t<decltype(x)> by hand for every such spot is redundant when auto already knows how to deduce the right type. Composed with ranges adaptors and lambdas, you often need to "force a copy" of an element right inside an expression (e.g. to guarantee capturing a value rather than a reference into a range's temporary proxy object), without pulling out a separate variable.

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

Формально auto(x) — это "явное decay-копирующее преобразование" (explicit decay-copy conversion): компилятор сначала применяет к x те же правила вывода типа, что применил бы к инициализатору переменной, объявленной auto (снимает ссылочность и верхнеуровневые const/volatile, массивы распадаются в указатели, функции — в указатели на функции), а затем строит временный объект этого выведенного типа, используя x как единственный аргумент конструктора.Formally, auto(x) is an "explicit decay-copy conversion": the compiler first applies to x the same type-deduction rules it would apply to the initializer of a variable declared with auto (stripping references and top-level const/volatile, decaying arrays to pointers, functions to function pointers), then constructs a temporary object of that deduced type, using x as the sole constructor argument.

void demo(const std::vector<int>& ref) {
    auto copy1 = auto(ref);    // copy1: std::vector<int>, не const, не ссылка// copy1: std::vector<int>, not const, not a reference
    copy1.push_back(42);       // можно изменять — ref остаётся нетронутым// mutable — ref itself stays untouched

    modify_in_place(auto(ref));  // временная decay-копия передаётся напрямую, без имени// a temporary decay-copy passed directly, with no name
}

Разница между auto(x) и auto{x} зеркально повторяет разницу между обычной функциональной инициализацией T(x) и списочной инициализацией T{x}: для большинства типов результат идентичен, но для типов с конструктором, принимающим std::initializer_list, списочная форма может выбрать именно этот конструктор там, где круглые скобки выбрали бы другой.The difference between auto(x) and auto{x} mirrors the difference between ordinary functional-style initialization T(x) and list initialization T{x}: for most types the result is identical, but for types with a constructor taking std::initializer_list, the brace form may pick exactly that constructor where the parenthesized form would have picked a different one.

std::vector<int> v = {1, 2, 3};
auto a = auto(v.size());   // auto(x) на size_t: обычное копирование числа// auto(x) on a size_t: ordinary copying of a number
auto b = auto{v.size()};   // auto{x} на size_t: тоже число, разницы здесь нет// auto{x} on a size_t: also a number, no difference here
// разница проявляется, только если тип имеет initializer_list-конструктор —// the difference only shows up if the type has an initializer_list constructor —
// см. классическую ловушку vector<int> v(5) vs v{5} в разделе "грабли"// see the classic vector<int> v(5) vs v{5} trap in the gotchas section

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

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

Гарантированная копия при возврате из геттераA guaranteed copy when returning from a getter

Функция, которая обязана вернуть независимую копию внутреннего поля, а не ссылку — auto(x) делает намерение "это копия, не ссылка" явным прямо в точке возврата, без дополнительной переменной.A function that must return an independent copy of an internal field, not a reference — auto(x) makes the "this is a copy, not a reference" intent explicit right at the return point, with no extra variable.

class Cache {
public:
    std::string snapshot() const {
        return auto(current_value_);  // явная копия, даже если бы current_value_ был ссылкой// an explicit copy, even if current_value_ were a reference
    }
private:
    std::string current_value_;
};
02

Захват значения, а не ссылки на прокси-объект rangeCapturing a value, not a reference into a range's proxy object

Range-адаптеры вроде views::transform нередко возвращают прокси-ссылки на временные объекты — auto(x) внутри лямбды форсирует независимую копию элемента прямо в точке использования, без риска захватить ссылку на то, что перестанет существовать.Range adaptors like views::transform often return proxy references to temporaries — auto(x) inside a lambda forces an independent copy of the element right at the point of use, with no risk of capturing a reference to something that's about to stop existing.

auto snapshots = data
    | std::views::transform([](const auto& item) {
          return auto(item.value);  // независимая копия, не ссылка на элемент диапазона// an independent copy, not a reference into the range
      });
03

Decay-копия в generic-коде без знания конкретного типаA decay-copy in generic code without knowing the concrete type

Шаблонная функция, принимающая forwarding-параметр, которой в одном месте нужна независимая копия значения (например, чтобы безопасно положить в контейнер, переживающий вызов функции) — без auto(x) пришлось бы явно писать std::decay_t<T>.A template function taking a forwarding parameter that needs, in one place, an independent copy of the value (e.g. to safely store it in a container that outlives the function call) — without auto(x) this would require explicitly writing std::decay_t<T>.

template<class T>
void store(T&& value) {
    history_.push_back(auto(value));  // decay-копия, не зависящая от того, T& или T&& пришло// a decay-copy, independent of whether T& or T&& was passed
}
04

Разрыв неявного псевдонима после auto& в структурированном связыванииBreaking an implicit alias after auto& in structured bindings

При переборе контейнера по ссылке иногда нужен независимый снимок значения текущей итерации для передачи в асинхронную задачу — auto(x) явно отделяет "то, что переживёт итерацию" от "ссылки, валидной только в текущей итерации цикла".When iterating a container by reference, an independent snapshot of the current iteration's value is sometimes needed to hand off to an async task — auto(x) explicitly separates "what will outlive the iteration" from "a reference valid only for the current loop iteration".

for (auto& task : tasks) {
    pool.submit([copy = auto(task)]() { copy.run(); });  // copy переживёт итерацию цикла// copy outlives the loop iteration
}

ГраблиGotchas

auto(x) vs auto{x} для типов с initializer_list-конструкторомauto(x) vs auto{x} for types with an initializer_list constructor

Ровно как с классической ловушкой std::vector<int> v(5) (вектор из 5 нулей) против std::vector<int> v{5} (вектор из одного элемента — числа 5), разница между auto(x) и auto{x} может быть значимой для типов с конструктором от std::initializer_list. Если тип, который выведет auto, имеет такой конструктор, auto{x} предпочтёт его там, где auto(x) выбрал бы обычный конструктор — стоит по умолчанию использовать круглые скобки auto(x), если нет осознанной причины требовать именно списочную инициализацию.Exactly as with the classic std::vector<int> v(5) (a vector of five zeros) versus std::vector<int> v{5} (a vector of one element — the number 5) trap, the difference between auto(x) and auto{x} can matter for types with an std::initializer_list constructor. If the type auto deduces has such a constructor, auto{x} will prefer it where auto(x) would have picked an ordinary constructor — it's worth defaulting to the parenthesized auto(x) unless there's a deliberate reason to require list initialization specifically.

Не путать с static_cast<T>(x) — тип не указывается явноNot to be confused with static_cast<T>(x) — the type isn't spelled out

auto(x) внешне похож на приведение типа, но принципиально отличается: static_cast<T>(x) требует явно назвать целевой тип T и может выполнять произвольное приведение (сужающее, пользовательское, между несвязанными типами), тогда как auto(x) не принимает никакого типа вообще — тип всегда выводится по тем же правилам, что и для auto-переменной, и единственная цель конструкции — decay-copy, а не приведение к произвольному другому типу.auto(x) superficially resembles a type cast, but is fundamentally different: static_cast<T>(x) requires explicitly naming a target type T and can perform an arbitrary conversion (narrowing, user-defined, between unrelated types), whereas auto(x) takes no type at all — the type is always deduced by the same rules as for an auto variable, and the construct's sole purpose is a decay-copy, not conversion to an arbitrary different type.

Тип выводится строго как для auto-переменной — сверяйтесь с cpp11/auto.htmlThe type is deduced exactly like for an auto variable — check cpp11/auto.html

Все тонкости вывода типа для обычной переменной auto x = expr; (снятие ссылок, снятие верхнеуровневых cv-квалификаторов, распад массивов и функций в указатели, поведение с auto*/auto&, которые здесь неприменимы, так как auto(x) — всегда объект по значению) переносятся на auto(x) без изменений — если вывод типа для auto в конкретном случае не очевиден, стоит освежить в памяти механику, описанную в cpp11/auto.html, прежде чем полагаться на auto(x) в неочевидном месте.All the subtleties of type deduction for an ordinary auto x = expr; variable (stripping references, stripping top-level cv-qualifiers, decaying arrays and functions to pointers, the behavior with auto*/auto&, which don't apply here since auto(x) is always an object by value) carry over to auto(x) unchanged — if type deduction for auto isn't obvious in a given case, it's worth refreshing the mechanics described in cpp11/auto.html before relying on auto(x) in a non-obvious spot.