Урок №187. Часткова спеціалізація шаблону

  Юрій  | 

  Оновл. 25 Бер 2021  | 

 37

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

Проблема

На уроці №184 ми дізналися, яким чином можна використовувати додатковий параметр шаблону. Розглянемо ще раз клас 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 Зірок (1 оцінок, середня: 5,00 з 5)
Loading...

Залишити відповідь

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