Немного из метапрограммирования 2/2.
3.
add_lvalue_reference.Давайте попробуем написать стандартную метафункцию, добавляющую lvalue ссылку к типу (далее будем опускать написание алиасов
_v/_t для краткости):template <typename T>
struct add_lvalue_reference : type_indentity<T&> {};
add_lvalue_reference_t:
int&& -> int&
int& -> int&
int -> int&А что для
void? Для void ответ void& даст ошибку компиляции, что не очень приятно. Можем сделать частичную специализацию. Только стоит помнить про все возможные комбинации cv-квалификаторов, что означает не одну специализацию, а четыре. Плюс такое решение не является устойчивым во времени: во-первых, на текущий момент void единственный тип, для которого не может быть добавлена lvalue reference, но не будет ли в будущем ещё одного, неизвестно; во-вторых, cv-квалификаторы тоже могут расшириться до условных cvlgbtq-квалификаторов, из-за чего придётся дописать множество специализаций. Очень некрасиво и топорно. Хочется какое-то более аккуратное решение, которое будет работать для всех типов, которые вызывают подобную проблему. Можно сделать вот так:template <typename T, typename Enable>
struct alr_impl : type_indentity<T> {};
template <typename T>
struct alr_impl<T, remove_reference_t<T&>> : type_indentity<T&> {};
template <typename T>
struct add_lvalue_reference : alr_impl<T, remove_reference_t<T>> {};Работает это просто. Для вызова
add_lvalue_reference<SomeType> происходит попытка сматчить вызов и частичную специализацию (т.к. частное всегда предпочтительнее общего). В этой специализации делается remove_reference_t<T&>, что для cv void не скомпилируется -> случается SFINAE -> специализация выбрасывается из списка кандидатов и выбирается базовый шаблон. Этот код конечно можно упростить:
template <typename T, class Enable>
struct alr_impl : type_indentity<T> {};
template <typename T>
struct alr_impl<T, void_t<T&>> : type_indentity<T&> {};
template <typename T>
struct add_lvalue_reference : alr_impl<T, void> {};В частичной специализации делается проверка на то, является ли выражение
T& well-formed. Если да, то специализация для <T, void> срабатывает. Аналогичным способом можно реализовать
add_rvalue_reference, add_pointer и много других type traits.Пример отсюда. Там можно посмотреть и несколько других более нетривиальных примеров.
4. Различные вычисления на компиляции могут замедлять время сборки вашего проекта. И вам может захотеться что-то сделать с этим. Но как померять время компиляции? Для clang в этом вам поможет флаг
-ftime-trace. Я так долго откладывал рассказ об этом флаге, что кто-то уже это сделал, причём с разбором того, как это работает. 5. И из повседневных мелочей.
- Вот так интересно можно юзать кастомный компаратор для
std::set:bool cmp(X a, X b) { ... }
using Cmp = std::integral_constant<decltype(&cmp), &cmp>;
std::set<X, Cmp> st;Лёгким движением руки мы сделали из функции структуру.
- Мне нравится, что посчитать N-е Фибоначчи втупую (через N-1 и N-2 числа) на шаблонах работает за O(n), когда через
constexpr функцию за O(n!). Просто потому что инстанцирование шаблонов == мемоизация вычисленных значений. - Вы можете написать почти все type trait’ы с помощью C++ (кроме очень специфических вроде
is_union, is_class и др.), но в стандартных библиотеках множество из них реализуются через compiler intrinsics, т.к. это тупо быстрее. Но вообще лишний раз подумайте, нужно ли вам заниматься чем-то подобным. На самом деле большинство проблем, которые вы можете захотеть решить метапрограммированием/SFINAE, решаются без этих инструментов гораздо проще и понятнее. Или ищите готовое (например в бусте). А лучше переходите на C++20.