User-defined сlass template argument deduction guides в C++17
Недавно внезапно обнаружил для себя user-defined deduction guides для вывода типов параметров шаблонных классов в С++17.
Class template argument deduction (CTAD) - это
возможность вывода компилятором типов параметров шаблонного класса по типу передаваемых
в конструктор аргументов, которую добавили в C++17. Сделано это было для того, чтобы
например можно было писать std::pair{10, 20.0f}
вместо std::pair<int, float>{10, 20.0f}
.
Компилятор при этом подбирает параметры шаблонного класса std::pair
по параметрам int
и float
аргументов, передаваемых в конструктор.
Про CTAD, думаю, известно почти всем, потому что это была одна из главных новых языковых фич, добавленных в C++17, которая наконец-то избавляла всех от мучений с написанием всех параметров шаблонных классов. Оказывается вместе с этим в C++17 была добавлена возможность вручную писать правила для вывода типов шаблонных классов.
Работает это следующим образом. Пусть имеется шаблонный класс my_class
:
template <typename T>
class my_class {
public:
my_class(T val) {}
};
Стандартный вывод параметров шаблонного класса будет работать так, что при передаче значения
типа int
в конструктор my_class
будет создаваться экзмепляр типа my_class<int>
. Но допустим
мы хотим, чтобы при этом создавался экземпляр типа my_class<long>
? Сделать это можно,
написав правило вывода параметров шаблонного класса следующим образом:
my_class(int) -> my_class<long>;
Теперь результат вызова конструктора my_class{10}
будет иметь тип my_class<long>
, а не
my_class<int>
.
Правила вывода параметров сами могут быть шаблонными. Например, можно сделать так, чтобы
при вызове конструктора с любым параметром типа T
тип параметра шаблона был какой-нибудь обёрткой
над T
:
template <typename T>
class my_wrapper {
public:
// разрешено неявное преобразование из T
my_wrapper(T val) {}
};
template <typename T>
class my_class {
public:
my_class(T val) {}
};
template <typename T>
my_class(T x) -> my_class<my_wrapper<T>>;
// ...
// тип x будет my_class<my_wrapper<std::string>>
my_class x{std::string{"aaaaa"}};
Кстати, эта техника используется в стандартной библиотеке ranges из C++20 для того, чтобы
для некоторого контейнера c
и функтора fn
при вызове transform_view{c, fn}
создавался экземпляр класса
transform_view<ref_view<Container>, Functor>
, а ссылка на контейнер неявно преобразовывалась
в экземпляр ref_view
. Именно оттуда я и узнал про определённые пользователем правила вывода типов
шаблонных классов, когда разбирался как это работает.