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. Именно оттуда я и узнал про определённые пользователем правила вывода типов шаблонных классов, когда разбирался как это работает.

comments powered by Disqus