Как выглядит шаблонная функция после компиляции при ее вызове с аргументами разного типа?

Ответ

Компилятор C++ выполняет инстанцирование шаблона, генерируя отдельную, конкретную версию функции (или класса) для каждого уникального набора шаблонных параметров, с которым она используется. Эти сгенерированные функции являются обычным машинным кодом.

Процесс:

  1. Компилятор видит вызов шаблонной функции с конкретными типами.
  2. Он подставляет эти типы вместо параметра шаблона T.
  3. Генерирует новую функцию с «зашитым» типом и компилирует ее.

Пример:

// Шаблонная функция (исходный код)
template <typename T>
T add(T a, T b) {
    return a + b;
}

int main() {
    auto r1 = add(5, 10);      // Вызов с int
    auto r2 = add(3.14, 2.71); // Вызов с double
    auto r3 = add('A', 1);     // Ошибка компиляции: типы 'char' и 'int' разные
    // Явное указание типа решает проблему:
    auto r4 = add<int>('A', 1); // char неявно приводится к int
}

После компиляции условно будут сгенерированы две независимые функции (манглинг имен упрощен):

// Сгенерированный код (пример)
int add_int(int a, int b) { // Инстанцирование для T = int
    return a + b;
}

double add_double(double a, double b) { // Инстанцирование для T = double
    return a + b;
}

Важные следствия:

  • Раздувание бинарного кода (code bloat): Каждая инстанцированная версия увеличивает размер исполняемого файла.
  • Компиляция только используемых инстанциаций: Если add ни разу не вызвана для std::string, эта версия сгенерирована не будет.
  • Определение в заголовочном файле: Поскольку инстанцирование происходит на этапе компиляции, тело шаблонной функции должно быть видно в каждой единице трансляции, где она используется. Поэтому шаблоны почти всегда определяются полностью в .hpp файлах.

Ответ 18+ 🔞

А, шаблоны в C++! Ну это ж классика, ёпта. Сейчас объясню на пальцах, только не бей.

Представь, что шаблон — это как чертёж для станка, а не сам готовый болт. Компилятор — это такой токарь-алкаш, который сидит и ждёт: "Ну чё, какие болты-то нужны?". Только когда ты в коде пишешь add(5, 10), он такой: "А, бля, целочисленные болты!" — и начинает на своём станке по чертежу вытачивать конкретную функцию для int. Это и называется инстанцирование — превращение абстрактного шаблона в реальный, жирный машинный код.

Вот смотри, как это выглядит в деле:

// Это просто чертёж, понимаешь? Функции ещё нет.
template <typename T>
T add(T a, T b) {
    return a + b;
}

int main() {
    auto r1 = add(5, 10);      // Токарь видит: о, целые числа! Делаем версию для int!
    auto r2 = add(3.14, 2.71); // Опа, дробные! Делаем ещё одну, отдельную версию для double!
    auto r3 = add('A', 1);     // А тут ёперный театр: 'char' и 'int' — разные типы! Станок встаёт. Ошибка компиляции.
    // Но можно ткнуть токаря мордой в чертёж:
    auto r4 = add<int>('A', 1); // Говорим явно: делай для int! И char послушно превратится в int.
}

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

// Вот что на самом деле оказывается в бинарнике
int add_int(int a, int b) { // Инстанцирование для T = int
    return a + b;
}

double add_double(double a, double b) { // Инстанцирование для T = double
    return a + b;
}

А теперь главные подводные камни, о которые все бьются:

  • Раздувание кода (code bloat): Это пиздец, чувак. Вызвал шаблон для int, double, long long — получил три независимые функции в бинарнике. Жрёт место, как не в себя. Но зато каждая — быстрая, оптимизированная под свой тип.
  • Генерируется только то, что используют: Логично же. Если ты ни разу не сложил std::string, то версии для строк и не будет. Компилятор не дурак, зря время тратить.
  • Определение — только в заголовочном файле: Вот это самое важное, бля! Поскольку токарю-компилятору нужно видеть полный чертёж в момент, когда он вытачивает болт, весь код шаблона (и объявление, и тело!) должен быть в .hpp файле. Иначе другой .cpp файл придёт и скажет: "а сделай-ка мне add для float", а как — непонятно, чертежа-то нет. Поэтому шаблоны — это всегда головная боль с инклудами, их в .cpp не спрячешь.

Короче, мощный инструмент, но если использовать бездумно — получишь бинарник размером с хороший порнофильм, который и компилироваться будет овердохуища.