PVOID - Перегрузки и шаблоны

Перегрузки и шаблоны

 Этот раздел является переводом туториала C++ Language


 

Перегруженные функции


В C++ две различные функции могут иметь одинаковые имена, если их параметры различны; различными должны быть либо число параметров, либо тип хотя бы одного из параметров. Например:

// перегрузка функций
#include <iostream>
using namespace std;

int operate (int a, int b)
{
  return (a*b);
}

double operate (double a, double b)
{
  return (a/b);
}

int main ()
{
  int x=5,y=2;
  double n=5.0,m=2.0;
  cout << operate (x,y) << '\n';
  cout << operate (n,m) << '\n';
  return 0;
}
10
2.5

 В этом примере есть две функции с именем operate, но одна из них имеет два параметра типа int, тогда как параметры другой функции имеют тип double. Компилятор узнает, какую из них вызвать в каждом случае, путем проверки типов, переданных в качестве аргументов, при вызове функции. Если она вызывается с двумя аргументами типа int, компилятор вызывает функцию, имеющую два параметра типа int, а если она вызывается с двумя аргументами типа double, компилятор вызовет функцию с параметрами типа double.

 В этом примере обе функции имеют совершенно различное поведение, версия с int перемножает аргументы, тогда как версия с double делит их. Это вообще не очень хорошая идея. Обычно ожидается, что две одноименные функции будут, по крайней мере, иметь схожее поведение, но этот пример демонстрирует, что вполне возможно не делать этого. Две перегруженные функции (т.е. две функции с одинаковыми именами) имеют полностью различные реализации; для любых целей они являются разными функциями, которые имеют одно и то же имя.

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

 

 Шаблоны функций


 Перегруженные функции могут иметь одинаковое определение. Например:

// перегруженные функции
#include <iostream>
using namespace std;

int sum (int a, int b)
{
  return a+b;
}

double sum (double a, double b)
{
  return a+b;
}

int main ()
{
  cout << sum (10,20) << '\n';
  cout << sum (1.0,1.5) << '\n';
  return 0;
}

30
2.5

 Здесь функция sum перегружена с разными типами параметров, но с одинаковым телом.

 Функция sum может быть перегружена для множества типов, и может иметь смысл для них всех иметь одно и то же тело. Для таких случаев C++ имеет возможность определять функции с общими типами, известные как шаблоны функций. Определение шаблона функции следует такому же синтаксису, как и обычная функция, за исключением того, что ему предшествует ключевое слово template и ряд параметров шаблона, заключенный в угловые скобки <>:

 template <template-parameters> function-declaration

 template-parameters — это последовательность параметров, разделенных запятыми. Эти параметры могут быть общими типами шаблона посредством указания ключевого слова class или typename, за которым следует идентификатор. Этот идентификатор может быть использован в объявлении функции, как если бы он являлся обычным типом. Например, общая функция sum может быть определена как:

template <class SomeType>
SomeType sum (SomeType a, SomeType b)
{
  return a+b;
}

 Не имеет значения, указан общий тип с ключевым словом class или typename в списке аргументов шаблона (они являются 100% синонимами в объявлении шаблона).

 Объявление SomeType в приведенном примере (общий тип в параметрах шаблона, заключенный в угловые скобки) позволяет использовать SomeType в любом месте определения функции, как и любой другой тип; он может быть использован как тип для параметров, как возвращаемый тип или для объявления новых переменных этого типа. Во всех случаях, он представляет общий тип, который должен быть определен на момент инстанцирования шаблона.

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

 name <template-arguments> (function-arguments)

 Например, шаблон функции sum, определенный выше, может быть вызван с помощью:

x = sum<int>(10,20);

 Функция sum<int> — это только одна из возможных конкретизаций шаблона функции sum. В этом случае, используя int в качестве аргумента шаблона при вызове, компилятор автоматически конкретизирует версию sum, где каждое вхождение SomeType замещается на int, как если бы она была определена как:

int sum (int a, int b)
{
  return a+b;
}

 Давайте посмотрим на реальный пример:

// шаблон функции
#include <iostream>
using namespace std;

template <class T>
T sum (T a, T b)
{
  T result;
  result = a + b;
  return result;
}

int main () {
  int i=5, j=6, k;
  double f=2.0, g=0.5, h;
  k=sum<int>(i,j);
  h=sum<double>(f,g);
  cout << k << '\n';
  cout << h << '\n';
  return 0;
}

11
2.5

 В этом случае мы использовали T в качестве имени параметра шаблона вместо SomeType. Это не имеет значения, и T на самом деле является довольно распространенным именем параметра шаблона для общих типов.

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

 Обратите также внимание на то, что T также используется для объявления локальной переменной этого (общего) типа внутри sum:

T result;

 Следовательно, result будет переменной такого же типа, что параметры a и b, а также возвращаемое значение.
 В этом конкретном случае, когда общий тип T используется в качестве параметра для sum, компилятор способен даже вывести тип данных автоматически без явного указания его в угловых скобках. Следовательно, вместо явного указания аргументов шаблона с помощью:

k = sum<int> (i,j);
h = sum<double> (f,g);

 можно просто написать:

k = sum (i,j);
h = sum (f,g);

 без типа, заключенного в угловые скобки. Для этого, конечно, тип должен быть однозначным. Если sum вызывается с аргументами разных типов, компилятор, возможно, не сможет автоматически вывести тип T.

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

// шаблоны функций
#include <iostream>
using namespace std;

template <class T, class U>
bool are_equal (T a, U b)
{
  return (a==b);
}

int main ()
{
  if (are_equal(10,10.0))
    cout << "x and y are equal\n";
  else
    cout << "x and y are not equal\n";
  return 0;
}

x and y are equal

 Обратите внимание, что в этом примере используется автоматический вывод параметра шаблона при вызове are_equal:

are_equal(10,10.0)

 эквивалентный следующему:

are_equal<int,double>(10,10.0)

 Здесь нет никакой неоднозначности, потому что числовые литералы всегда имеют определенный тип: если иное не указано суффиксом, целочисленные литералы всегда имеют значения типа int, а вещественные литералы всегда имеют значения типа double. Поэтому 10 всегда имеет тип int, а 10.0 всегда имеет тип double.

 

 Параметры шаблона, не являющиеся типами


 Параметры шаблона могут включать не только типы, введенные ключевыми словами class или typename, но могут также включать выражения конкретного типа:

// аргументы шаблона
#include <iostream>
using namespace std;

template <class T, int N>
T fixed_multiply (T val)
{
  return val * N;
}

int main() {
  std::cout << fixed_multiply<int,2>(10) << '\n';
  std::cout << fixed_multiply<int,3>(10) << '\n';
}

20
30

 Второй аргумент шаблона функции fixed_multiply имеет тип int. Он выглядит как обычный параметр функции и может фактически использоваться так же.

 Но существует большая разница: значения параметров шаблона определяются во время компиляции для генерации другого экземпляра функции fixed_multiply, и, следовательно, значение этого аргумента никогда не передается во время исполнения: два вызова fixed_multiply в main по существу вызывают две версии функции: одна всегда умножает на два, а другая всегда умножает на три. По этой же причине второй аргумент шаблона должен быть константным выражением (ему нельзя передавать переменную).