Посмотрел доклад Andrei Alexandrescu на CppCon 2021. Накидаю приконых фактов оттуда:
- предлагает засунуть в стандарт
template <typename… Ts> struct type_sequence {};потому что если этого не сделать, каждый будет писать это сам (и вероятно с помощью
tuple/variant, что концептуально не всегда то, что нужно). В таком виде со списками типов предлагали работать и на CppRussia 2019 с примерами других функций, которые могут понадобиться при работе со списками типов, что выглядит вообще-то довольно приятно и в ногу со временем. И рядом Alexandrescu предлагает три базовые функции в классической манере для работы с металистом типов: head, tail и cons (создать лист из элемента и другого листа). Всё как в Lisp. - и депрекейтнуть
integer_sequence, потому что это не последовательность целых чисел, а последовательность значений какого-то типа. Вместо него ввести value_sequence, потому что это семантически честнее.Из интересного ещё можно отметить такое. Предположим есть функция
template <typename T, typename… Ts, typename U>
int f(T, U, const Ts&…);и её вызовы
f(1, 2); // Ts=<>
f<int, int>(1, 2, 3); // Ts=<int>
f<int, int, double>(1, 2, 3, 4); // Ts=<int, double>Тип
U всегда сможет вывестись! Причём из параметра, а не указанного списка типов в шаблоне. В первом случае это int по понятным причинам. Во втором это тоже int. В третьем же Ts выводится как <int, double>, а U — исходя из параметра. Причина в том, что если variadic pack начал матчится, он не остановится и идёт до конца (жадный матчинг). Получается, в таком кейсе у нас нет возможности указать тип U компилятору явно, ведь он всегда будет выводится из параметра. Соответственно, если у вас есть что-то такое
template <typename... Ts, typename T = int>
T f(Ts…);тип
T может быть только int, потому что в него никогда ничего не сматчится. Соответственно, если у вас пак типов функции находится не в конце, то он всегда должен быть указан явно:
template <typename... Ts, typename T>
int f(Ts... values, T value);f(1); // Ts=<>
f<int, double>(1, 2, 3); // Ts=<int, double>
f<int, int>(1, 2); // error, потому что не хватает одного аргументаВсё это приводит к забавным кейсам вроде такого
template <typename... Ts, typename... Us>
int f(Ts... ts, Us... us);Тут
Us всегда выводится из параметром (deduced), тогда как Ts должно быть указано явно. Можете попробовать понять для себя, чему они будут равны в следующих случаях:f(1);
f(1, “2”);
f<int, char>(1, ‘2’);
f<int, char>(1, '2', “three”);Или может быть такой угар:
template <typename... Ts, typename... Us, typename T>
int f(Ts..., Us..., T);При любом вызове
Us будет пустым паком. Что бы вы ни делали. Все компиляторы это кушают, но стандарт говорит, что если один пак всегда пустой, то это no diagnostic required (грубо говоря, нельзя так).Ещё есть такие штуки как
std::tr2::bases и std::tr2::direct_bases, которые возвращают тайплист всех базовых классов указанного и все непосредственные базовые классы указанного соответственно.Хотя вообще доклад не об этом : ) Так что посмотрите.