Урок №179. Частичная специализация шаблона

  Юрий  | 

  |

  Обновл. 15 Сен 2021  | 

 46119

 ǀ   12 

На этом уроке мы рассмотрим, что такое частичная специализация шаблона в языке С++, как она используется и какие есть нюансы.

Проблема

На уроке №176 мы узнали, каким образом можно использовать дополнительный параметр шаблона. Рассмотрим еще раз класс StaticArray из материалов того же урока:

Здесь у нас есть 2 параметра шаблона класса: параметр типа и параметр non-type.

Теперь предположим, что нам нужно написать функцию для вывода всех элементов массива. Хотя мы можем сделать это через метод класса, мы реализуем это через отдельную функцию (ради лучшего погружения в тему).

Используя шаблон функции, мы можем написать следующее:

Это позволит нам сделать:

И получить:

0 1 2 3 4

Хотя всё работает правильно, но есть один нюанс. Рассмотрим следующий код функции main():

Примечание: Мы рассматривали strcpy_s на уроке о строках C-style.

Программа скомпилируется со следующим результатом:

H e l l o ,   w o r l d !

Для всех типов, кроме char, имеет смысл помещать пробел между каждым элементом массива, чтобы элементы не «слипались». Однако с типом char есть смысл вывести всё вместе, как строку C-style, чтобы не было лишних пробелов. Как мы можем это исправить?

Полная специализация шаблона — решение?


Сначала мы могли бы подумать об использовании специализации шаблона функции. Однако проблема с полной специализацией шаблона заключается в том, что все параметры шаблона должны быть явно определены. Например:

Как вы можете видеть, мы добавили шаблон функции print() для работы с типом char. Результат:

Hello, world!

Хотя одна проблема решена, возникает другая проблема: использование полной специализации шаблона класса означает, что мы должны явно указывать длину передаваемого массива! Рассмотрим следующий пример:

Вызов print(char12) вызовет шаблон функции print() с общим шаблоном StaticArray<T, size>, так как char12 является типа StaticArray<char, 12>, а шаблон функции print() принимает только StaticArray<char, 14> (длина массива отличается).

Хотя мы могли бы скопировать еще раз шаблон функции print() для работы со StaticArray<char, 12>, но это неэффективно. А что, если нам нужно будет позднее использовать массив с 5 или 20 элементами? Опять копировать шаблон? Это лишняя работа.

Очевидно, что полная специализация шаблона класса здесь является решением-костылем. Частичная специализация шаблона — вот, что нам нужно.

Частичная специализация шаблона

Частичная специализация шаблона позволяет выполнить специализацию шаблона класса (но не функции!), где некоторые (но не все) параметры шаблона явно определены. Для нашей вышеприведенной задачи идеальное решение заключается в том, чтобы шаблон функции print() работал со StaticArray типа char, но при этом размер массива не являлся фиксированным значением, а мог варьироваться.

Вот наш шаблон функции print(), который принимает частично специализированный шаблон класса StaticArray:

Как вы можете видеть, мы здесь явно указали тип char, но size оставили не фиксированным, поэтому функция print() будет работать с массивами типа char любого размера. Вот и всё!

Полный код программы:

Результат:

Hello, world! Hello, dad!

Как и ожидалось.

Обратите внимание, начиная с C++14 частичная специализация шаблона может использоваться только с классами, но не с отдельными функциями (для функций используется только полная специализация шаблона). Наш пример void print(StaticArray<char, size> & array) работает только потому, что шаблон функции print() принимает в качестве параметра шаблон класса, который, в свою очередь, частично специализирован.

Частичная специализация шаблонов методов


Ограничение частичной специализации для функций может привести к некоторым проблемам при работе с методами класса. Например, что, если бы мы определили StaticArray следующим образом:

Функция print() является методом класса StaticArray<T, int>. Что произойдет, если мы захотим частично специализировать шаблон функции print(), чтобы метод работал по-другому? Мы можем попробовать сделать следующее:

К сожалению, это не сработает, так как мы пытаемся частично специализировать шаблон функции, что делать запрещено.

Как же это можно обойти? Одним из очевидных решений является частичная специализация шаблона всего класса:

Результат:

0 1 2 3 4
4.000000e+00 4.100000e+00 4.200000e+00 4.300000e+00

Хотя это работает, но это не самый лучший вариант, так как у нас теперь куча дублированного кода из StaticArray<T, size> в StaticArray<double, size>.

Если бы можно было использовать код из StaticArray<T, size> в StaticArray<double, size> без дублирования. Ничего вам это не напоминает? Как по мне, то это звучит, как отличный вариант для применения наследования!

Вы можете начать с:

Но как мы можем ссылаться на StaticArray? Никак, но, к счастью, есть обходной путь с использованием общего родительского класса:

Результат тот же, что и в примере, приведенном выше, но дублированного кода меньше.

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (149 оценок, среднее: 4,82 из 5)
Загрузка...

Комментариев: 12

  1. Георгий:

    Может, я что-то недопонимаю, но частичная специализация шаблона функции(!) у меня работает (стандарт в настройках c++14):

    Вывод:

    4
    3
    Partial
    Partial

    1. Finchi:

      Это работает только потому, что шаблон функции принимает в качестве параметра шаблон класса, который, в свою очередь, частично специализирован. (с)

    2. Andrey:

      Это не частичная специализация, это перегрузка. Просто одна перегрузка использует один шаблон, а вторая перегрузка использует другой шаблон. Это можно проверить убрав верхнюю функцию, и нижняя нормально вызовется. Была бы частичной специализацией, то была бы ошибка компиляции)

  2. S1yGus:

    Зачем в последнем примере с наследованием метод print() делается виртуальным? Не проще ли его просто переопределить?

  3. Александр:

    А почему в частичной специализации методов класса, при использовании наследования здесь:

    мы не можем наследовать исходный шаблон?

    Вместо использования дополнительного родительского StaticArray_Base

  4. somebox:

    Правильно я понимаю, что в примере

    мы по сути одним шаблоном "перегрузили" другой.

  5. Илья:

    В начале урока проскальзывала такая функция,как strcpy_s,принимающая массив,в который мы хотим что то записать,длину этого массива и собственно то,что мы хотим записать,но эта функция вызывала ошибку компиляции.
    Решением было использовать функцию strcpy,которая принимает то,куда мы хотим записать,и то,что мы хотим записать.
    Вопрос вот в чём,откуда взялась функция strcpy?В компиляторе на GCC на Code::Blocks такой даже нету в наличии.

    1. somebox:

      Она находится в составе системной библиотеки cstring.

    2. Анастасия:

      однако, в уроке про строки в стиле С функция strcpy_s() принимала всего два параметра — массив, в который копируем и массив, который копируем. Не лишний ли в примере из этого урока второй параметр?

      1. Михаил:

        Второй аргумент в данном случае это размер приемника(максимальное количество символов которых мы можем записать )
        https://en.cppreference.com/w/c/string/byte/strcpy

  6. Doriz:

    Почему специализация

    является полной, а не частичной?

    1. Фото аватара Юрий:

      Это не специализация, это шаблон функции print() с одним параметром non-type. Спасибо, исправил.

Добавить комментарий

Ваш E-mail не будет опубликован. Обязательные поля помечены *