Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Шаблоны классов и метапрограммирование

Avatar for Min9fi1st Min9fi1st
September 09, 2025
46

Шаблоны классов и метапрограммирование

Avatar for Min9fi1st

Min9fi1st

September 09, 2025

Transcript

  1. Точки объявления (PoD) • Ниже приведены два фрагмента кода. int

    y = 2; { int y = y; } int x = 2; { int x[x]; } • Оба сомнительны, но в одном случае будет использовано значение переменной из внешней области видимости, а в другом случае нет. (ООП ФТИ 2 сем.) Шаблоны классов 10.09.2025 2 / 32
  2. Точки объявления (PoD) • Ниже приведены два фрагмента кода. int

    y = 2; { int y /* PoD */ = y; } int x = 2; { int x[x] /* PoD */; } • Оба сомнительны, но в одном случае будет использовано значение переменной из внешней области видимости, а в другом случае нет. • Точка объявления [basic.scope.pdecl] это точка в которой объявление завершено (после полного объявления но перед инициализатором). • До точки объявления имя не считается введённым в область видимости (соответственно используется имя из прошлой области видимости). (ООП ФТИ 2 сем.) Шаблоны классов 10.09.2025 3 / 32
  3. Шаблоны классов • Обобщённый тип, задаваемый шаблоном считается объявленным в

    PoD. template <typename T> struct fwnode /* POD */ { T data_; fwnode<T> *next_; }; • В теле таким образом может быть использован указатель или ссылка на себя (как на неполный тип). (ООП ФТИ 2 сем.) Шаблоны классов 10.09.2025 4 / 32
  4. Зависимые имена внутри шаблонов • Нет никаких проблем (как с

    функциями) в использовании вторичной типизации. template <typename T> class Stack { fwnode<T> *top_; // всё остальное в стеке для объектов типа T общего вида }; • Такой стек (построенный на односвязном списке, что вполне реалистично) ипользует fwnode. Параметр обязателен. (ООП ФТИ 2 сем.) Шаблоны классов 10.09.2025 5 / 32
  5. Порождение классов из шаблонов template <typename T> class Stack {

    fwnode<T> *top_; public: push(T x); }; Надеваем волшебные очки: class Stack<int> { fwnode<int> *top_; public: push(int x); }; Stack<int> s; (ООП ФТИ 2 сем.) Шаблоны классов 10.09.2025 6 / 32
  6. Проблема: слишком общие шаблоны • Кажется этот стек не слишком

    эффективен для хранения целых чисел. template <typename T> class Stack { fwnode<T> *top_; // всё остальное в стеке для объектов типа T общего вида }; • Для целых чисел было бы проще хранить массив. (ООП ФТИ 2 сем.) Шаблоны классов 10.09.2025 7 / 32
  7. Специализация • Кажется этот стек не слишком эффективен для хранения

    целых чисел. template <typename T> class Stack { fwnode<T> *top_; // всё остальное в стеке для объектов типа T общего вида }; • Для целых чисел было бы проще хранить массив. template <> class Stack<int> { int *content_; // всё остальное в стеке для целых чисел }; (ООП ФТИ 2 сем.) Шаблоны классов 10.09.2025 8 / 32
  8. Обсуждение • Концепт это список требований к типу. • Как

    и для функций ручная специализация уже инстанцированного типа не имеет смысла. template <typename T> class Invalid {}; // тут уже произошло инстанцирование Invalid <int> i; // эта специализация не имеет смысла и запрещена template <> class Invalid<int> {}; • Опасайтесь специализаций, происходящих внутри единицы трансляции. (ООП ФТИ 2 сем.) Шаблоны классов 10.09.2025 9 / 32
  9. Частичная специализация • Кажется этот стек не слишком эффективен для

    хранения всех указателей template <typename T> class Stack { fwnode<T> *top_; // всё остальное в стеке для объектов типа T общего вида }; • Для указателей было бы проще хранить массив указателей template <typename T> class Stack<T*> { T **content_; // всё остальное в стеке для указателей }; (ООП ФТИ 2 сем.) Шаблоны классов 10.09.2025 10 / 32
  10. Упрощение имён в специализациях • Рассмотренное ранее упрощение имён отлично

    работает в (частичных) специализациях. template <typename T> class A { A* a1; // A здесь означает A<T> }; template <typename T> class A<T*> { A* a2; // A здесь означает A<T*> }; • Разумеется это опционально. Указывать полные имена – вполне легально (и часто это отличная идея). (ООП ФТИ 2 сем.) Шаблоны классов 10.09.2025 11 / 32
  11. Примеры частичной специализации 1. template <typename T, typename U> class

    Foo {}; 2. template <typename T> class Foo<T, T> {}; 3. template <typename T> class Foo<T, int> {}; 4. template <typename T, typename U> class Foo<T*, U*> {}; Foo<int, float> mif; // 1 Foo<float, float> mff; // 2 Foo<float, int> mfi; // 3 Foo<int*, float*> mp; // 4 (ООП ФТИ 2 сем.) Шаблоны классов 10.09.2025 12 / 32
  12. Явная специализация • Кроме инстанцирования (явного или неявного), шаблонная функция

    или структура может быть явно специализирована. template <typename T> T max(T x, T y) { .... } template <> int max<int>(int x, int y) { .... } template <> float max(float x, float y) { .... } • Специализация обязана физически следовать за основным шаблоном. template <> int min<int>(int x, int y) { .... } // ошибка template <typename T> T min(T x, T y) { .... } // primary тут (ООП ФТИ 2 сем.) Шаблоны классов 10.09.2025 13 / 32
  13. Немного расширения сознания template <typename T> struct X { void

    print() { std::cout « "forall«< std::endl; } }; template <typename T> struct X<vector<T>> { void print() std::cout « "forvec«< std::endl; }; X<int> a; X<vector<int» b; a.print(); b.print(); (ООП ФТИ 2 сем.) Шаблоны классов 10.09.2025 14 / 32
  14. Ограничения специализации • Специализация (как и для функций) всегда должна

    в коде следовать за объявлением шаблона общего вида. • Частично специализированный шаблон должен быть действительно менее общим, чем тот, версией которого он является. template <typename T> class X /* .... */ ; template <typename U> class X<U> /* .... */ ; // ошибка (ООП ФТИ 2 сем.) Шаблоны классов 10.09.2025 15 / 32
  15. Менее специальна, менее обобщена? • Частичная специализация съедает какое-то количество

    параметров primary шаблона. • До тех пор пока она менее специальна в некоем смысле, у неё может быть даже больше шаблонных параметров. template <typename T> class X; template <typename R, typename Arg> class X<R(Arg)> {} • Давайте пока отложим обсуждение что означает менее специальная для специализации. (ООП ФТИ 2 сем.) Шаблоны классов 10.09.2025 16 / 32
  16. Специализация и LSP • Можно ли шаблонную специализацию назвать разновидностью

    наследования? • Увы нет. С точки зрения наследования это нарушение LSP. template <typename T> struct S { void foo (); }; template <> struct S<int> { void bar (); }; S<double> sd; sd.foo(); S<int> si; si.bar(); • Специализация может не иметь ничего общего с полной версией. (ООП ФТИ 2 сем.) Шаблоны классов 10.09.2025 17 / 32
  17. Шаблоны членов: простая задача struct Data { template <typename T>

    T read() const; }; template <typename T> class DataReader { const T& source_; public: DataReader(const T& s) : source( s){} template <typename R> R read(); // вызывает source.read() }; // тут необходимо написать определение DataReader::read (ООП ФТИ 2 сем.) Шаблоны классов 10.09.2025 18 / 32
  18. Первый вариант решения template <typename T> class DataReader { const

    T& source_; public: template <typename R> R read(); }; template <typename T> template <typename R> R DataReader<T>::read() { R res = source_.read<R>(); // синтаксическая неоднозначность return res; } (ООП ФТИ 2 сем.) Шаблоны классов 10.09.2025 19 / 32
  19. Разрешение неоднозначности template <typename T> class DataReader { const T&

    source_; public: template <typename R> R read(); }; template <typename T> template <typename R> R DataReader<T>::read() { R res = source_.template read<R>(); return res; } (ООП ФТИ 2 сем.) Шаблоны классов 10.09.2025 20 / 32
  20. Более сложная задача template <typename T> class DataReader { const

    T& source_; public: template <typename R> R read(); }; // тут необходимо написать специализацию // для DataReader<T>::read<string> (ООП ФТИ 2 сем.) Шаблоны классов 10.09.2025 21 / 32
  21. Попытка решения template <typename T> class DataReader { const T&

    source; public: template <typename R> R read(); }; // код ниже не работает, он иллюстрирует возможный ход мыслей template <typename T> template <> string DataReader<T>::read<string>() const { string foo = source_.template read<string>(); return foo; } (ООП ФТИ 2 сем.) Шаблоны классов 10.09.2025 22 / 32
  22. Правильный ответ template <typename T> class DataReader { const T&

    source_; public: template <typename R> R read(); }; • Требуемая специализация невозможна, так как она означала бы частичную специализацию метода. • Если у нас есть конкретная структура Data, то можно написать полную специализацию для DataReader<Data>::read<string>. (ООП ФТИ 2 сем.) Шаблоны классов 10.09.2025 23 / 32
  23. Пример полной специализации struct Data { template <typename T> T

    read() const; }; template <typename T> сlass DataReader { const T& source_; public: template <typename R> R read(); }; template <> template <> // это не опечатка! string DataReader<Data>::read<string>() { return source_.template read<string>(); } (ООП ФТИ 2 сем.) Шаблоны классов 10.09.2025 24 / 32
  24. Задача: параметризация методов template <typename T1, typename T2> struct A

    { void func(); }; • Необходимо добиться следующего эффекта: A <int, double> a; A <float, double> b; a.func(); // for int b.func(); // for all • Т.е. параметризовать метод первым аргументом шаблона. • Задачу усложняет то, что частичная специализация для методов невозможна. (ООП ФТИ 2 сем.) Шаблоны классов 10.09.2025 25 / 32
  25. Параметризация: первая попытка template <typename T1, typename T2> struct A

    { void func() { T1 dummy; internal_func(dummy); } private: template <typename V> void internal_func(V) { cout « "all"; } void internal_func(int) { cout « "int"; } }; A <int, double> a; A <float, double> b; a.func(); // for int, благодаря разрешению перегрузки b.func(); // for all (ООП ФТИ 2 сем.) Шаблоны классов 10.09.2025 26 / 32
  26. Переходники типов template <typename T> struct Type2Type { typedef T

    OriginalType; }; • Минимальный размер. • Прозрачность. • Номинативная типизация. (ООП ФТИ 2 сем.) Шаблоны классов 10.09.2025 27 / 32
  27. Параметризация: переходники типов template <typename T1, typename T2> struct A

    { void func() internal_func(Type2Type<T1>()); private: template <typename V> void internal_func(V) std::cout « "all"; void internal_func(Type2Type<int>) cout « "int«< std::endl; }; A <int, double> a; A <float, double> b; a.func(); // for int, благодаря разрешению перегрузки b.func(); // for all (ООП ФТИ 2 сем.) Шаблоны классов 10.09.2025 28 / 32
  28. Открытие метапрограммирования / Prime number computation by Erwin Unruh template

    <int i> struct D { D(void*); operator int(); }; template <int p, int i> struct is_prime { enum { prim = (p % i) && is_prime<(i > 2 ? p : 0), i -1> :: prim }; }; template <int i> struct Prime_print { Prime_print<i-1> a; enum prim = is_prime<i, i-1>::prim ; void f() D<i> d = prim; }; struct is_prime<0, 0> { enum { prim = 1 }; }; struct is_prime<0, 1> { enum prim = 1 ;}; struct Prime_print<2> { enum { prim = 1 }; void f() { D<2> d = prim; } }; main () { Prime_print<10> a; } (ООП ФТИ 2 сем.) Шаблоны классов 10.09.2025 29 / 32
  29. Факториал • Идея лежит на поверхности: что если развернуть систематическое

    sfinae от типов на целые числа? template<size_t N> struct fact : integral_constant<size_t, N * fact<N - 1>{}> {}; template<> struct fact<0> : integral_constant<size_t, 1> {}; cout « fact<5>::value « endl; • Например инстанцирования в этом примере легко проследить (ООП ФТИ 2 сем.) Шаблоны классов 10.09.2025 30 / 32
  30. Факториал • Идея лежит на поверхности: что если развернуть систематическое

    sfinae от типов на целые числа? template<size_t N> struct fact : integral_constant<sizet, N * fact < N − 1 > {} > {}; template<> struct fact<0> : integral_constant<size_t, 1> {}; fact<1> : integral_constant<size_t, 1 * fact<0>{}> // → 1 fact<2> : integral_constant<size_t, 2 * fact<1>{}> // → 2 fact<3> : integral_constant<size_t, 3 * fact<2>{}> // → 6 fact<4> : integral_constant<size_t, 4 * fact<3>{}> // → 24 fact<5> : integral_constant<size_t, 5 * fact<4>{}> // → 120 cout « fact<5>::value « endl; // → 120 • Умножение работает за счёт наличия operator size_t() (ООП ФТИ 2 сем.) Шаблоны классов 10.09.2025 31 / 32
  31. Числа Фибоначчи • С той же лёгкостью можно вычислять на

    этапе компиляции числа Фибоначчи. template<size_t N> struct fibonacci : integral_constant< size_t, fibonacci<N-1>{} + fibonacci<N-2>{}> {}; template<> struct fibonacci<1> : integral_constant<size_t,1> {}; template<> struct fibonacci<0> : integral_constant<size_t,0> {}; • Не смущает ли нас здесь двойная рекурсия? (ООП ФТИ 2 сем.) Шаблоны классов 10.09.2025 32 / 32