Proces tworzenia instancji szablonu#
Tworzenie instancji szablonu (template instantiation) to proces, w którym kompilator generuje konkretny kod funkcji na podstawie definicji szablonu i dostarczonych (lub wydedukowanych) typów argumentów.
Model kompilacji szablonów#
Szablony stosują specyficzny model kompilacji, który różni się od zwykłych funkcji:
Cały kod szablonu musi być dostępny w miejscu użycia (zazwyczaj w pliku nagłówkowym)
Kompilator generuje kod dla każdej unikalnej kombinacji parametrów szablonu
// max_value.hpp
#ifndef MAX_VALUE_HPP
#define MAX_VALUE_HPP
template<typename T>
T max_value(T a, T b)
{
return b < a ? a : b;
}
#endif // MAX_VALUE_HPP
// main.cpp
#include "max_value.hpp"
int main()
{
max_value(1, 2); // instancja szablonu: max_value<int>(int, int)
max_value(1.5, 2.5); // instancja szablonu: max_value<double>(double, double)
}
Rodzaje procesu tworzenia instancj szablonu#
Niejawne tworzenie instancji szablonu (implicit instantiation)#
Kompilator automatycznie generuje kod szablonu w miejscu pierwszego użycia:
// translation_unit.cpp
#include "max_value.hpp"
int x = max_value(1, 2); // niejawnie utworzona instancja dla max_value<int>(int, int)
double y = max_value(1.5, 2.5); // niejawnie utworzona instancja dla max_value<double>(double, double)
Warning
Niejawne tworzenie instancji może prowadzić do wielokrotnego generowania tego samego kodu w różnych jednostkach translacji, co zwiększa rozmiar plików obiektowych i czas kompilacji.
Jawne tworzenie instancji szablonu (explicit instantiation)#
Programista może wymusić wygenerowanie konkretnych instancji szablonu w jednostce translacji. Daje to kontrolę nad tym, gdzie kod jest generowany i może pomóc w optymalizacji czasu kompilacji oraz rozmiaru plików obiektowych.
// math.hpp
#ifndef MATH_HPP
#define MATH_HPP
template <typename T>
T square(T value);
#endif // MATH_HPP
// math.cpp
#include "math.hpp"
#include <complex>
template <typename T>
T square(T value)
{
return value * value;
}
template int square<int>(int);
template double square<double>(double);
template std::complex<double> square<std::complex<double>>(std::complex<double>);
Warning
Proces tworzenia jawnej instancji blokuje możliwość tworzenia niejawnych instancji dla innych typów. Dostępne będą tylko te instancje, które zostały jawnie zadeklarowane.
Note
Dla szablonów klas składnia tworzenia jawnej instancji jest nieco inna:
template class MyClass<int>; // jawna instancja szablonu klasy
## Wymagania wobec typów
Utworzenie instancji szablonu jest możliwe tylko wtedy, gdy dla typu podanego jako argument szablonu zdefiniowane są wszystkie operacje używane przez szablon.
```cpp
template<typename T>
T max(T a, T b)
{
return b < a ? a : b; // wymaga operatora< oraz konstruktora kopiującego
}
struct Point
{
int x, y;
// brak operatora< - nie można użyć max()
};
struct Value
{
int data;
bool operator<(const Value& other) const
{
return data < other.data;
}
};
Point p1{1, 2}, p2{3, 4};
// max(p1, p2); // ERROR - no operator< for Point
Value v1{10}, v2{20};
max(v1, v2); // OK - operator< jest zdefiniowany
Fazy kompilacji szablonu#
Kompilacja szablonów przebiega w dwóch fazach (two-phase lookup):
Faza 1: Analiza definicji szablonu
Na etapie definicji szablonu (przed tworzeniem jakiejkolwiek instancji) sprawdzana jest:
poprawność składniowa
użycie nazw niezależnych od parametrów szablonu (non-dependent names)
statyczne asercje, które nie zależą od parametru szablonu
Faza 2: Analiza instancji szablonu
Podczas tworzenia instancji szablonu kod jest ponownie sprawdzany, ze szczególnym uwzględnieniem:
nazw zależnych od parametrów szablonu (dependent names)
wymagań dotyczących operacji na typach parametrów
statycznych asercji zależnych od parametrów
template<typename T>
void foo(T t)
{
undeclared(); // Błąd fazy 1: jeśli undeclared() jest nieznane
undeclared(t); // Błąd fazy 2: jeśli undeclared(T) jest nieznane dla danego T
t.unknown(); // Błąd fazy 2: jeśli T nie ma metody unknown()
static_assert(sizeof(int) > 10, "int too small"); // Faza 1: zawsze błąd jeśli warunek nie jest spełniony
static_assert(sizeof(T) > 10, "T too small"); // Faza 2: błąd tylko dla T o rozmiarze <= 10
}