Урок №182. Екземпляри шаблонів функцій

  Оновл. 14 Лип 2023  | 

 1935

Мова C++ не компілює шаблони функцій напряму. Замість цього, коли компілятор зустрічає виклик шаблону функції, він копіює шаблон функції і замінює типи параметрів шаблону функції фактичними (переданими) типами даних. Функція з фактичними типами даних називається екземпляром шаблону функції (або «об’єктом шаблону функції»).

Розглянемо це на практиці. По-перше, створимо шаблон функції:

Потім зробимо виклик шаблону функції:

Компілятор бачить, що обидва числа є цілочисельними, тому він копіює шаблон функції і створює екземпляр шаблону max(int, int):

Це тепер уже «звичайна функція». Припустимо, що нам потрібно знову викликати функцію max(), але вже з іншим типом даних:

Мова C++ автоматично створює екземпляр шаблону max(double, double):

І потім компілює його. Також варто відзначити, що, якщо ви створите шаблон функції, але не викличете його, екземпляри цього шаблону не будуть створені.

Оператори, виклики функцій і шаблони функцій

Шаблони функцій працюють як з фундаментальними типами даних (char, int, double тощо), так і з класами (але є нюанс). Екземпляр шаблону компілюється як звичайна функція. У звичайній функції будь-які оператори або виклики інших функцій, які використовуються в цій функції, повинні бути визначені/перевантажені, або ви отримаєте помилку компіляції. Аналогічно, будь-які оператори або виклики інших функцій, які присутні в шаблоні функції, повинні бути визначені/перевантажені для роботи з фактичними (переданими) типами даних. Розглянемо це на практиці.

По-перше, створимо простий клас:

Тепер подивимося, що станеться при спробі виклику функції max() з об’єктами класу Dollars:

Мова C++ створить наступний екземпляр шаблону функції max():

А потім компілятор спробує скомпілювати цю функцію, але нічого не вийде, тому що C++ не знає, як обробляти вираз a > b! Отже, це призведе до помилки:

Помилка C2676 бінарний ">": "const T" не визначає цей оператор або перетворення до типу припустимо до вбудованого оператора

Повідомлення про помилку вказує на той факт, що ми не перевантажили оператор > для класу Dollars. Давайте перевантажимо:

Тепер мова C++ знає, як обробляти вираз a > b, коли в якості змінних використовуються об’єкти класу Dollars!

Ще один приклад


Створимо шаблон функції, яка обчислює середнє арифметичне елементів масиву:

Протестуємо:

Результат:

4
6.8275

Як ви бачите, все відмінно працює з фундаментальними типами даних!

Оскільки тип повернення шаблону функції той же, що і тип переданих елементів масиву в функцію, то обчислення середнього арифметичного цілочисельних значень призведе до цілочисельного результату (з відкиданням будь-якої дробової частини), як і обчислення значень типу double призведе до результату типу double. Це може бути не очевидним, тому хорошим тоном буде вказати на це в коментарях.

Тепер подивимося, що станеться при виконанні функції average() з об’єктами класу Dollars:

Результат:

1>c:\users\kicli\source\repos\consoleapplication10\consoleapplication10\consoleapplication10.cpp(37): error C2679: бінарний "<<": не знайдено оператор, який приймає правий операнд типу "T" (або прийнятне перетворення відсутнє) 1> with
1> [
1> T=Dollars
1> ]
1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.15.26726\include\ostream(508): note: может быть "std::basic_ostream<char,std::char_traits> &std::basic_ostream<char,std::char_traits>::operator <<(std::basic_streambuf<char,std::char_traits> *)"
1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.15.26726\include\ostream(480): note: або "std::basic_ostream<char,std::char_traits> &std::basic_ostream<char,std::char_traits>::operator <<(const void *)" 1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.15.26726\include\ostream(460): note: або "std::basic_ostream<char,std::char_traits> &std::basic_ostream<char,std::char_traits>::operator <<(long double)" 1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.15.26726\include\ostream(440): note: або "std::basic_ostream<char,std::char_traits> &std::basic_ostream<char,std::char_traits>::operator <<(double)" 1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.15.26726\include\ostream(420): note: або "std::basic_ostream<char,std::char_traits> &std::basic_ostream<char,std::char_traits>::operator <<(float)" 1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.15.26726\include\ostream(400): note: або "std::basic_ostream<char,std::char_traits> &std::basic_ostream<char,std::char_traits>::operator <<(unsigned __int64)" 1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.15.26726\include\ostream(380): note: або "std::basic_ostream<char,std::char_traits> &std::basic_ostream<char,std::char_traits>::operator <<(__int64)" 1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.15.26726\include\ostream(360): note: або "std::basic_ostream<char,std::char_traits> &std::basic_ostream<char,std::char_traits>::operator <<(unsigned long)" 1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.15.26726\include\ostream(340): note: або "std::basic_ostream<char,std::char_traits> &std::basic_ostream<char,std::char_traits>::operator <<(long)" 1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.15.26726\include\ostream(320): note: або "std::basic_ostream<char,std::char_traits> &std::basic_ostream<char,std::char_traits>::operator <<(unsigned int)" 1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.15.26726\include\ostream(295): note: або "std::basic_ostream<char,std::char_traits> &std::basic_ostream<char,std::char_traits>::operator <<(int)" 1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.15.26726\include\ostream(275): note: або "std::basic_ostream<char,std::char_traits> &std::basic_ostream<char,std::char_traits>::operator <<(unsigned short)" 1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.15.26726\include\ostream(241): note: або "std::basic_ostream<char,std::char_traits> &std::basic_ostream<char,std::char_traits>::operator <<(short)" 1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.15.26726\include\ostream(221): note: або "std::basic_ostream<char,std::char_traits> &std::basic_ostream<char,std::char_traits>::operator <<(bool)" 1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.15.26726\include\ostream(215): note: або "std::basic_ostream<char,std::char_traits> &std::basic_ostream<char,std::char_traits>::operator <<(std::ios_base &(__cdecl *)(std::ios_base &))" 1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.15.26726\include\ostream(209): note: або "std::basic_ostream<char,std::char_traits> &std::basic_ostream<char,std::char_traits>::operator <<(std::basic_ios<char,std::char_traits> &(__cdecl *)(std::basic_ios<char,std::char_traits> &))"
1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.15.26726\include\ostream(204): note: або "std::basic_ostream<char,std::char_traits> &std::basic_ostream<char,std::char_traits>::operator <<(std::basic_ostream<char,std::char_traits> &(__cdecl *)(std::basic_ostream<char,std::char_traits> &))"
1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.15.26726\include\ostream(702): note: або "std::basic_ostream<char,std::char_traits> &std::operator <<<char,std::char_traits>(std::basic_ostream<char,std::char_traits> &,const char *)"
1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.15.26726\include\ostream(749): note: або "std::basic_ostream<char,std::char_traits> &std::operator <<<char,std::char_traits>(std::basic_ostream<char,std::char_traits> &,char)"
1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.15.26726\include\ostream(787): note: або "std::basic_ostream<char,std::char_traits> &std::operator <<<std::char_traits>(std::basic_ostream<char,std::char_traits> &,const char *)"
1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.15.26726\include\ostream(834): note: або "std::basic_ostream<char,std::char_traits> &std::operator <<<std::char_traits>(std::basic_ostream<char,std::char_traits> &,char)"
1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.15.26726\include\ostream(960): note: або "std::basic_ostream<char,std::char_traits> &std::operator <<<std::char_traits>(std::basic_ostream<char,std::char_traits> &,const signed char *)"
1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.15.26726\include\ostream(967): note: або "std::basic_ostream<char,std::char_traits> &std::operator <<<std::char_traits>(std::basic_ostream<char,std::char_traits> &,signed char)"
1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.15.26726\include\ostream(974): note: або "std::basic_ostream<char,std::char_traits> &std::operator <<<std::char_traits>(std::basic_ostream<char,std::char_traits> &,const unsigned char *)"
1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.15.26726\include\ostream(981): note: або "std::basic_ostream<char,std::char_traits> &std::operator <<<std::char_traits>(std::basic_ostream<char,std::char_traits> &,unsigned char)"
1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.15.26726\include\ostream(1047): note: або "std::basic_ostream<char,std::char_traits> &std::operator <<<char,std::char_traits>(std::basic_ostream<char,std::char_traits> &,const std::error_code &)"
1>c:\users\kicli\source\repos\consoleapplication10\consoleapplication10\consoleapplication10.cpp(37): note: при спробі зіставити список аргументів "(std::ostream, T)"
1> with
1> [
1> T=Dollars
1> ]

Компілятор збожеволів. Ми згадували про такі помилки на попередньому уроці. Незважаючи на настільки об’ємний «результат», тут все досить просто. У перших рядках повідомляється, що компілятор не зміг знайти перевантаження оператора << для класу Dollars. Далі вказуються функції з типами даних, які викликалися для порівняння, але так і не підійшли. І в кінці вказуються параметр шаблону і фактичний тип параметра.

Visual Studio береже наші нерви і надає нам альтернативний вивід помилок:

Помилка E0349 відсутній оператор "<<", який відповідає цим операндам
Помилка C2679 бінарний "<<": не знайдено оператор, який приймає правий операнд типу "T" (або прийнятне перетворення відсутнє)

Пам’ятайте, що average() повертає об’єкт класу Dollars, а ми намагаємося цей об’єкт вивести за допомогою оператора виводу << і std::cout. Однак, ми не перевантажили оператор << для класу Dollars. Давайте виправимо це:

Якщо ж тепер запустити програму, то отримаємо наступне:

Помилка C2676 бінарний "+=": "T" не визначає цей оператор або перетворення до типу припустимо до вбудованого оператора
Помилка C2676 бінарний "/=": "T" не визначає цей оператор або перетворення до типу припустимо до вбудованого оператора

Ці помилки були викликані екземпляром шаблону функції, створеним при виклику average(Dollars*, int). Пам’ятайте, що при виклику шаблону функції, компілятор копіює шаблон функції з типами параметрів, а потім замінює типи параметрів шаблону на фактичні (передані) типи даних. Ось екземпляр шаблону функції average(), де T є класом Dollars:

Причина, по якій ми отримали повідомлення про помилку, криється в наступному рядку:

В цьому випадку sum є об’єктом класу Dollars. А щоб все запрацювало, нам потрібно перевантажити оператори += і /= для класу Dollars:

Нарешті, наш код скомпілюється, і результат:

13 dollars

Хоча виконана робота може здатися дуже великою, але це тільки через те, що наш клас Dollars з самого початку був “худий як тріска”. Ключовий момент заключається в тому, що нам не потрібно модифікувати average(), щоб він працював з об’єктами класу Dollars (або з будь-яким іншим типом даних). Вся робота була пророблена тільки з класом Dollars, а про все інше компілятор подбав самостійно!

Оцінити статтю:

1 Зірка2 Зірки3 Зірки4 Зірки5 Зірок (58 оцінок, середня: 4,97 з 5)
Завантаження...

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

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