Уже больше полугода откладывал этот пост, потому что его написание требовало не просто время на сесть и выродить из себя k предложений текста с примерами кода, а пересмотреть несколько нетривиальных докладов.
Но я справился, так что верю, что и вы.
Немного из метапрограммирования 1/2.
1.
type_identity (С++20).Это очень простой хелпер для реализации множества других type trait’ов:
template <typename T>
struct type_identity { using type = T; };Например можно использовать вот так:
template <typename T>
struct remove_reference : type_indentity<T> {};
template <>
struct remove_reference<T&> : type_indentity<T> {};
template <>
struct remove_reference<T&&> : type_indentity<T> {};Конечно же, можно сокращать гораздо более сложные конструкции.
2.
void_t (C++17). void_t это алиас на void:template <typename…>
using void_t = void;Это очень маленький кусочек кода, который позволяет творить на самомо деле очень крутые вещи. Обычно он используется для валидации выражений на компиляции и включения SFINAE.
Давайте посмотрим, какие с
void_t есть проблемы.struct One { using x = int; };
struct Two { using y = int; };
template <typename T, std::void_t<typename T::x>* = nullptr>
void f() {}
template <typename T, std::void_t<typename T::y>* = nullptr>
void f() {}
int main() {
f<One>();
f<Two>();
}При попытке скомпилировать данный код, мы получим
redefinition of function f. Но мы же знаем, что всё должно работать! Давайте попробуем заменить void_t на что-то другое (покажем только изменившийся код):template <typename ...Args> using MyType = void;
template <typename T, MyType<typename T::x>* = nullptr>
void f() {}
template <typename T, MyType<typename T::y>* = nullptr>
void f() {}Такой код по факту ничем не отличается и не работает (однако ведёт нас к успеху). Давайте попробуем исправить проблему и увидеть, что же было не так. Т.к.
void является довольно специфическим типом, давайте заменим его на что-то более обычное:struct MyTypeT {};
template <typename ...Args> using MyType = MyTypeT;Сейчас мы увидим те же ошибки компиляции, что и ранее. Но если мы сделаем ещё одно небольшое изменение:
struct MyTypeT {};
template <typename ...Args> struct MyType : MyTypeT {};всё заработает.
Или если мы реализуем
void_t вот так:template <typename T, typename…>
struct void_t_impl : type_identity<T> {}
template <typename… Args>
using void_t = typename void_t_impl<void, Args…>::type;и заюзаем такой в первом примере, всё заработает!
Давайте поймём, почему не работает. template type alias’ы с C++11 являются упрощённой конструкцией в языке, специально чтобы все штуки вроде
type_trait_t/type_trait_v работали как можно быстрее. И по факту они просто заменяются на то, что алиасят. В C++14 решили сказать, что все dependent штуки из шаблонов templated type alias’ов должны где-то запоминаться (хотя вообще-то никакого запоминания инстанцированных версий у них нет). Получилось противоречие в стандарте, когда вообще-то должно запоминаться и разруливаться в смысле SFINAE, но получается, что просто заменяется на void и ничего не работает. Тогда как со структурой такого не происходит. Пример отсюда. Более подробное пояснение тут.