Урок №174. Экземпляры шаблонов функций

  Юрий Ворон  | 

    | 

  Обновлено 7 Ноя 2018  | 

 255

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

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

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

ВОЛШЕБНАЯ ТАБЛЕТКА ПО С++