ядро языка — синтаксисcore language — syntax
std::pair(1, 2.0) вместо std::pair<int, double>(1, 2.0) — компилятор выводит параметры шаблона класса из аргументов конструктора, как он давно делает для функций-шаблонов.std::pair(1, 2.0) instead of std::pair<int, double>(1, 2.0) — the compiler deduces class template parameters from constructor arguments, the way it has long done for function templates.
Class Template Argument Deduction (CTAD) позволяет опустить явный список параметров шаблона класса при создании объекта, если компилятор может вывести их из аргументов, переданных конструктору — точно так же, как уже давно работает вывод типов для шаблонных функций. Механизм, который решает, какие именно параметры вывести, называется deduction guide — он либо генерируется неявно из конструкторов класса, либо задаётся явно автором класса.Class Template Argument Deduction (CTAD) lets you omit the explicit list of a class template's parameters when creating an object, if the compiler can deduce them from the arguments passed to the constructor — exactly the way type deduction has long worked for template functions. The mechanism that decides which parameters to deduce is called a deduction guide — it's either implicitly generated from the class's constructors, or explicitly supplied by the class's author.
std::pair<int, double> p(1, 2.0);
std::vector<int> v = {1, 2, 3};
std::lock_guard<std::mutex> lock(mtx); // мьютекс уже в скобках, но тип пиши сам// the mutex is right there, but write the type yourself
std::pair p(1, 2.0); // выведено: pair<int, double>// deduced: pair<int, double>
std::vector v = {1, 2, 3}; // выведено: vector<int>// deduced: vector<int>
std::lock_guard lock(mtx); // выведено: lock_guard<std::mutex>// deduced: lock_guard<std::mutex>
Вывод типов шаблонных параметров для функций работал с самого начала шаблонов в языке — но для классов этой возможности не существовало вплоть до C++17, что создавало устойчивую и нелогичную асимметрию.Type deduction for function template parameters had worked since templates first appeared in the language — but for classes this capability didn't exist until C++17, creating a persistent and illogical asymmetry.
std::pair<int, double>(1, 2.0) дублирует информацию: типы int и double уже видны из аргументов 1 и 2.0, но компилятор до C++17 отказывался использовать эту информацию для класса, хотя для функции make_pair(1, 2.0) та же логика вывода типов работала ещё с C++11.std::pair<int, double>(1, 2.0) duplicates information: the types int and double are already visible from the arguments 1 and 2.0, but before C++17 the compiler refused to use that information for a class, even though the same deduction logic already worked for the function make_pair(1, 2.0) since C++11.
std::make_pair, std::make_tuple, std::make_unique и подобные функции-помощники не добавляли новой функциональности сверх конструктора — они существовали ровно потому, что вывод типов работал для функций, но не для конструкторов классов напрямую. CTAD делает часть этих фабричных функций избыточными.std::make_pair, std::make_tuple, std::make_unique and similar helper functions added no functionality beyond the constructor — they existed exactly because deduction worked for functions but not for class constructors directly. CTAD makes some of these factory functions redundant.
Для сложных типов вроде std::map<std::string, std::vector<int>> явное дублирование типа в объявлении и в инициализации было чистой избыточностью — вся эта информация уже присутствовала в списке инициализации или в аргументах конструктора.For complex types like std::map<std::string, std::vector<int>>, explicitly duplicating the type in the declaration and the initialization was pure redundancy — all that information was already present in the initializer list or the constructor arguments.
Когда компилятор видит ClassName(args) без списка параметров шаблона в угловых скобках, он строит набор так называемых deduction guides — псевдо-функций-шаблонов вида "конструктор → список параметров шаблона" — и выбирает подходящую тем же алгоритмом overload resolution, что и для обычных функций. Guides бывают двух видов:When the compiler sees ClassName(args) with no angle-bracket template parameter list, it builds a set of so-called deduction guides — pseudo-function-templates of the form "constructor → template parameter list" — and picks the matching one using the same overload resolution algorithm as for ordinary functions. Guides come in two kinds:
// 1. неявные guides — генерируются автоматически из каждого конструктора класса// 1. implicit guides — auto-generated from every constructor of the class
template<class T1, class T2>
struct Pair {
Pair(T1 a, T2 b);
};
// компилятор неявно строит guide: Pair(T1, T2) -> Pair<T1, T2>;// the compiler implicitly builds a guide: Pair(T1, T2) -> Pair<T1, T2>;
Pair p(1, "x"); // выведено Pair<int, const char*> из этого guide// Pair<int, const char*> deduced from this guide
// 2. явный guide — пишется вручную, когда конструктор не даёт достаточно информации// 2. explicit guide — written by hand when the constructor doesn't give enough information
template<class T>
struct Wrapper {
Wrapper(std::initializer_list<T>);
};
template<class T>
Wrapper(std::initializer_list<T>) -> Wrapper<T>; // явный deduction guide// explicit deduction guide
Стандартная библиотека уже поставляется с нужными guides для std::pair, std::vector, std::lock_guard и большинства других шаблонных типов — поэтому CTAD "просто работает" для них без дополнительных действий со стороны пользователя.The standard library already ships with the necessary guides for std::pair, std::vector, std::lock_guard, and most other template types — so CTAD "just works" for them with no extra effort from the user.
С фигурными скобками std::vector v{1}; компилятор выбирает конструктор от initializer_list, и результат — вектор из одного элемента 1, а не вектор из одного нулевого элемента, как можно было бы подумать по аналогии с std::vector<int> v(1);. Эта путаница — не новая проблема CTAD, а старая проблема приоритета initializer_list-конструкторов, но CTAD делает её более заметной, потому что тип теперь не написан явно рядом.With braces, std::vector v{1}; makes the compiler pick the initializer_list constructor, and the result is a vector holding one element, 1 — not a vector of one zero-initialized element, as you might expect by analogy with std::vector<int> v(1);. This confusion isn't a new problem introduced by CTAD — it's the long-standing initializer_list constructor priority issue — but CTAD makes it more visible, because the type is no longer spelled out right there.
Неявные guides строятся напрямую из сигнатур конструкторов, поэтому если конструктор принимает, скажем, универсальную ссылку или указатель, а реальный тип, который нужно вывести, — что-то другое (как в примере с Wrapper выше, где тип элемента должен извлекаться из initializer_list<T>, а не из самого параметра конструктора), компилятору нужна подсказка в виде явного deduction guide — иначе вывод либо не сработает, либо выведет не тот тип.Implicit guides are built directly from constructor signatures, so if a constructor takes, say, a universal reference or a pointer, while the actual type that needs deducing is something else (as in the Wrapper example above, where the element type must come from initializer_list<T> rather than from the constructor parameter itself), the compiler needs a hint in the form of an explicit deduction guide — otherwise deduction either fails or picks the wrong type.
Вывод параметров — всё или ничего: как только в угловых скобках указан хотя бы один параметр шаблона класса явно, CTAD для оставшихся параметров не применяется — компилятор не смешивает выведенные и явно заданные параметры в одном списке для класса, как это иногда бывает для функций.Deduction is all-or-nothing: as soon as at least one class template parameter is explicitly given in angle brackets, CTAD doesn't kick in for the remaining ones — the compiler doesn't mix deduced and explicitly specified parameters in a single class template parameter list, unlike what sometimes happens for functions.