Шаблоны (C++)

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

Определение и использование шаблонов

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

template <typename T>
T minimum(const T& lhs, const T& rhs)
{
    return lhs < rhs ? lhs : rhs;
}

Приведенный выше код описывает шаблон универсальной функции с одним параметром типа T, возвращаемое значение и параметры вызова (lhs и rhs) всех этих типов. Вы можете назвать параметр типа, который вы хотите, но по соглашению одноглавные буквы чаще всего используются. T является параметром шаблона; typename ключевое слово говорит, что этот параметр является заполнителем для типа. При вызове функции компилятор заменит каждый экземпляр T конкретным аргументом типа, заданным пользователем или выведенным компилятором. Процесс, в котором компилятор создает класс или функцию из шаблона, называется экземпляром шаблона. minimum<int> Это экземпляр шаблонаminimum<T>.

В другом месте пользователь может объявить экземпляр шаблона, специализированного для int. Предположим, что get_a() и get_b() — это функции, возвращающие int:

int a = get_a();
int b = get_b();
int i = minimum<int>(a, b);

Тем не менее, поскольку это шаблон функции, и компилятор может выводить тип T из аргументов a и b, можно вызвать его так же, как обычная функция:

int i = minimum(a, b);

При обнаружении последней инструкции компилятор создает новую функцию, в которой каждое вхождение T в шаблоне заменяется следующим intобразом:

int minimum(const int& lhs, const int& rhs)
{
    return lhs < rhs ? lhs : rhs;
}

Правила того, как компилятор выполняет вычет типов в шаблонах функций, основаны на правилах для обычных функций. Дополнительные сведения см. в разделе "Разрешение перегрузки вызовов шаблонов функций".

Параметры типа

В приведенном minimum выше шаблоне обратите внимание, что параметр типа T не является соответствующим образом, пока он не будет использоваться в параметрах вызова функции, где добавляются квалификаторы констант и ссылочных квалификаторов.

Нет практического ограничения на количество параметров типа. Разделите несколько параметров запятыми:

template <typename T, typename U, typename V> class Foo{};

Ключевое слово class эквивалентен этому контекстуtypename. Вы можете выразить предыдущий пример следующим образом:

template <class T, class U, class V> class Foo{};

Оператор многоточия (...) можно использовать для определения шаблона, который принимает произвольное число параметров нулевого или более типа:

template<typename... Arguments> class vtclass;

vtclass< > vtinstance1;
vtclass<int> vtinstance2;
vtclass<float, bool> vtinstance3;

Любой встроенный или определяемый пользователем тип можно использовать в качестве аргумента типа. Например, можно использовать std::vector в стандартной библиотеке для хранения переменных типаint, doublestd::string, constMyClassMyClass*, MyClass&и т. д. Основное ограничение при использовании шаблонов заключается в том, что аргумент типа должен поддерживать все операции, применяемые к параметрам типа. Например, если мы вызываем minimum использование MyClass , как в следующем примере:

class MyClass
{
public:
    int num;
    std::wstring description;
};

int main()
{
    MyClass mc1 {1, L"hello"};
    MyClass mc2 {2, L"goodbye"};
    auto result = minimum(mc1, mc2); // Error! C2678
}

Ошибка компилятора создается, так как MyClass не предоставляет перегрузку для < оператора.

Нет никаких обязательных требований, что аргументы типа для любого конкретного шаблона принадлежат одной иерархии объектов, хотя можно определить шаблон, который применяет такое ограничение. Вы можете объединить объектно-ориентированные методы с шаблонами; Например, можно сохранить производный* в векторе<Base*>. Обратите внимание, что аргументы должны быть указателями

vector<MyClass*> vec;
   MyDerived d(3, L"back again", time(0));
   vec.push_back(&d);

   // or more realistically:
   vector<shared_ptr<MyClass>> vec2;
   vec2.push_back(make_shared<MyDerived>());

Основные требования, std::vector которые и другие стандартные контейнеры библиотеки накладывают на элементы T , которые T можно назначить с помощью копирования и создания копирования.

Параметры, не относящиеся к типу

В отличие от универсальных типов на других языках, таких как C# и Java, шаблоны C++ поддерживают параметры, отличные от типов, также называемые параметрами значения. Например, можно указать целочисленное значение константы, чтобы указать длину массива, как и в этом примере, аналогичному классу std::array в стандартной библиотеке:

template<typename T, size_t L>
class MyArray
{
    T arr[L];
public:
    MyArray() { ... }
};

Обратите внимание на синтаксис в объявлении шаблона. Значение size_t передается в качестве аргумента шаблона во время компиляции и должно быть const или выражением constexpr . Вы используете его следующим образом:

MyArray<MyClass*, 10> arr;

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

Вычет типов для параметров шаблона, не относящихся к типу

В Visual Studio 2017 и более поздних версиях и в /std:c++17 режиме или более поздних версиях компилятор выводит тип аргумента шаблона, отличного от типа, объявленного с помощью auto:

template <auto x> constexpr auto constant = x;

auto v1 = constant<5>;      // v1 == 5, decltype(v1) is int
auto v2 = constant<true>;   // v2 == true, decltype(v2) is bool
auto v3 = constant<'a'>;    // v3 == 'a', decltype(v3) is char

Шаблоны в качестве параметров шаблона

Шаблон может быть параметром шаблона. В этом примере MyClass2 имеет два параметра шаблона: параметр typename T и параметр шаблона Arr:

template<typename T, template<typename U, int I> class Arr>
class MyClass2
{
    T t; //OK
    Arr<T, 10> a;
    U u; //Error. U not in scope
};

Так как сам параметр Arr не имеет текста, его имена параметров не требуются. На самом деле, это ошибка, чтобы ссылаться на имя типа или имена параметров класса Arr из текста MyClass2. По этой причине имена параметров типа Arr могут быть опущены, как показано в этом примере:

template<typename T, template<typename, int> class Arr>
class MyClass2
{
    T t; //OK
    Arr<T, 10> a;
};

Аргументы шаблона по умолчанию

Шаблоны классов и функций могут иметь аргументы по умолчанию. Если у шаблона есть аргумент по умолчанию, его можно не указать при использовании. Например, шаблон std::vector имеет аргумент по умолчанию для распределителя:

template <class T, class Allocator = allocator<T>> class vector;

В большинстве случаев класс std::allocator по умолчанию является допустимым, поэтому используется вектор, как показано ниже:

vector<int> myInts;

Но при необходимости можно указать пользовательский распределитель следующим образом:

vector<int, MyAllocator> ints;

При наличии нескольких аргументов шаблона все аргументы после первого аргумента по умолчанию должны иметь аргументы по умолчанию.

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

template<typename A = int, typename B = double>
class Bar
{
    //...
};
...
int main()
{
    Bar<> bar; // use all default type arguments
}

Специализация шаблонов

В некоторых случаях невозможно или желательно определить точно тот же код для любого типа. Например, может потребоваться определить путь к коду, который необходимо выполнить, только если аргумент типа является указателем или std::wstring или типом, производным от определенного базового класса. В таких случаях можно определить специализацию шаблона для конкретного типа. Когда пользователь создает экземпляр шаблона с этим типом, компилятор использует специализацию для создания класса, а для всех других типов компилятор выбирает более общий шаблон. Специализации, в которых все параметры являются специализированными, являются полными специализациями. Если только некоторые из параметров специализированы, он называется частичной специализацией.

template <typename K, typename V>
class MyMap{/*...*/};

// partial specialization for string keys
template<typename V>
class MyMap<string, V> {/*...*/};
...
MyMap<int, MyClass> classes; // uses original template
MyMap<string, MyClass> classes2; // uses the partial specialization

Шаблон может иметь любое количество специализаций, если каждый параметр специализированного типа является уникальным. Только шаблоны классов могут быть частично специализированы. Все полные и частичные специализации шаблона должны быть объявлены в том же пространстве имен, что и исходный шаблон.

Дополнительные сведения см. в разделе "Специализация шаблона".