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

  Юрий  | 

    | 

  Обновл. 27 Июн 2019  | 

 3780

 ǀ   5 

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

Предыстория

В уроке №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 (20 оценок, среднее: 5,00 из 5)
Загрузка...

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

  1. Аватар somebox:

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

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

  2. Аватар Илья:

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

    1. Аватар somebox:

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

  3. Аватар Doriz:

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

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

    1. Юрий Юрий:

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

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

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