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

Здравствуйте, я пытаюсь узнать о явном создании экземпляров. И поэтому читая разные примеры, но в одном примере есть некоторые сомнения. Пример приведен ниже, и у меня есть 2 сомнения в этом конкретном примере.

Файл Application.cc содержит:

extern template int compare(const int&, const int&);
int i = compare(a1[0], a2[0]);// instantiation will appear elsewhere

Файл templateBuild.cc содержит:

template int compare(const int&, const int&);

Также обратите внимание, что сравнение шаблона функции:

template<typename T, typename F = less<T>>
int compare(const T &v1, const T &v2, F f = F())
{
  if (f(v1,v2)) return -1;
  if (f(v2,v1)) return 1;
  return 0;
}

Мои вопросы заключаются в следующем:

  1. Как видите, в файле Application.cc во 2-й строке написано (в качестве комментария), что экземпляр появится в другом месте. Но здесь мы используем функцию шаблона как int i = compare(a1[0], a2[0]);, и мы знаем, что всякий раз, когда мы используем функцию шаблона, компилятор будет создавать ее экземпляр. Так почему же этот комментарий написан там? Также в пояснении написано, что

Когда компилятор видит определение экземпляра (в отличие от объявления), он генерирует код. Таким образом, файл templateBuild.o будет содержать определения для сравнения, созданные с помощью int.

Итак, мой вопрос: если компилятор генерирует код всякий раз, когда он видит определение экземпляра, и поэтому templateBuild.o будет содержать определение сравнения, созданное с помощью int, то как мы можем использовать compare() в файле Application.cc, используя compare(a1[0], a2[0]);? Я имею в виду, что шаблон сравнения() еще не создан, так как мы можем использовать его до того, как он будет создан?

  1. Мой второй вопрос заключается в том, где я должен писать (помещать) содержимое шаблона compare(). Например в заголовочном файле или в файле Application.cc? Под содержимым шаблона compare() я подразумеваю 3-й блок кода, который я привел в примере.

person Jason Liam    schedule 30.04.2021    source источник
comment
Для использования (вызова) функции в соответствующей единице перевода должно присутствовать только объявление. Что есть (часть с extern). Это объявление указывает компилятору, как генерировать машинный код для вызова функции, включая передачу аргументов и, возможно, чтение возвращаемого значения. Для выполнения этой задачи определение функции не требуется.   -  person Daniel Langr    schedule 30.04.2021
comment
мы знаем, что всякий раз, когда мы используем шаблонную функцию, компилятор создает ее экземпляр — я не думаю, что это правда. Компилятор может создать экземпляр шаблона функции, только если он увидит его определение.   -  person Daniel Langr    schedule 30.04.2021
comment
код генерируется только тогда, когда мы используем шаблон (а не когда мы его определяем). Это точная цитата из книги C++ 11 из раздела, посвященного шаблонам функций. @DanielLangr   -  person Jason Liam    schedule 30.04.2021
comment
Обратите внимание на следующую цитату из en.cppreference.com/w/cpp/language/function_template: явное объявление создания экземпляра (внешний шаблон) предотвращает неявное создание экземпляра: код, который в противном случае вызвал бы неявное создание экземпляра, должен использовать предоставленное определение явного создания экземпляра в другом месте программы. Живая демонстрация: godbolt.org/z/zxqozWMv7 (нет машинный код для f<int> сгенерирован). Ваша книга кажется немного неточной. Я предполагаю, что это потому, что на практике в подавляющем большинстве случаев шаблоны не создаются явно. Кстати, что это за книга?   -  person Daniel Langr    schedule 30.04.2021
comment
Книга является стандартной книгой (я полагаю) C++ Primer Fifth eidtion. Также есть еще две вещи, которые там написаны (которые я нахожу противоречивыми), которые я цитирую ниже. Цитата 1: мы можем объявить функцию, которая не определена, если мы никогда не используем эту функцию. Цитата 2: когда мы вызываем функцию, компилятору нужно видеть только объявление функции. Теперь Цитата1 и Цитата2 не противоречат друг другу? Разве вызов функции не использует его? @DanielLangr   -  person Jason Liam    schedule 30.04.2021
comment
Нет, это не так. Как только мы вызываем/используем функцию, ее определение, конечно же, должно существовать. Но это необходимо только на этапе связывания. Компилятору это не нужно при компиляции (трансляции) исходного файла, в котором вызывается функция.   -  person Daniel Langr    schedule 30.04.2021


Ответы (2)


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

// compare.h

template<typename T>
int compare(const T &v1, const T &v2)
{
  if (v1 < v2) return -1;
  if (v2 < v1) return 1;
  return 0;
}
// Application.cc

#include <compare.h>

extern template int compare(const int&, const int&);
int i = compare(1, 2); 
// templateBuild.cc

#include <compare.h>

template int compare(const int&, const int&);

Теперь полезно представить, как выглядят единицы перевода для обоих этих исходных файлов.

  1. Единица перевода для Application.cc:
template<typename T>
int compare(const T &v1, const T &v2)
{
  if (v1 < v2) return -1;
  if (v2 < v1) return 1;
  return 0;
}

extern template int compare(const int&, const int&);
int i = compare(1, 2);

Когда компилятор переводит (компилирует) эту единицу трансляции, он видит только явное объявление экземпляра. Если бы его не было, вызов функции compare привел бы к неявному созданию экземпляра определения. Но поскольку он есть, этого неявного создания экземпляров избегают, как написано здесь:

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

Следовательно, компилятор генерирует машинный код только для вызова compare<int>, поскольку это была обычная (не шаблонная) функция с ее объявлением, но не определением в единице перевода.

Демонстрация в реальном времени: https://godbolt.org/z/1c8jvvcv1

Обратите внимание, что для compare<int> не создается машинный код.

  1. Единица перевода для templateBuild.cc:
template<typename T>
int compare(const T &v1, const T &v2)
{
  if (v1 < v2) return -1;
  if (v2 < v1) return 1;
  return 0;
}

template int compare(const int&, const int&);

Здесь у нас есть явное определение создания экземпляра, которое приводит к тому, что шаблон функции создается как compare<int>.

Демонстрация в реальном времени: https://godbolt.org/z/o9vxPvP75

Теперь машинный код для compare<int> сгенерирован.

person Daniel Langr    schedule 30.04.2021
comment
cppinsights.io может помочь визуализировать: с внешним, неявное создание экземпляра без внешнего. - person Jarod42; 30.04.2021
comment
Я думаю, что путаюсь в точной работе компилятора и компоновщика. Насколько я понимаю, это шаги, как работает эта программа (в основном). Шаг 1: Компилятор видит оператор extern template int compare(const int&, const int&);, который говорит ему, что ему не нужно неявно генерировать код, если только он не встретит явное определение шаблона где-то в программе (это может быть другой файл). Шаг 2: Он видит оператор: int i = compare(a1[0], a2[0]); , но не создает экземпляр по причине, указанной на шаге 1. @DanielLangr - person Jason Liam; 30.04.2021
comment
Шаг 3: он видит template int compare(const int&, const int&); в templateBuild.cc и генерирует код (или создает экземпляр сравнения‹int›). Теперь мой вопрос состоит в том, чтобы инициализировать i в операторе int i = compare(a1[0],a2[0]);, нам нужен результат, возвращаемый из инстанцирования compare‹int›. Так как же возвращается этот результат? Это будет работа компоновщика, а не компилятора? И правильный ли мой трехэтапный процесс компиляции. Что вы добавите в процесс? @DanielLangr - person Jason Liam; 30.04.2021
comment
@JasonLiam Механизмы для передачи аргументов и возвращаемых значений функций называются соглашениями о вызовах и определяются ABI системы. Например, возвращаемое значение int в x86_64/Linux передается в регистр eax. - person Daniel Langr; 30.04.2021

Вопрос 1)

extern template int compare(const int&, const int&);

С помощью этой строки вы можете подавить неявное создание экземпляра специализации шаблона или его членов. Вот что означает комментарий во второй строке. В текущем примере явное определение создания экземпляра находится в файле templateBuild.cc. Подробное объяснение можно найти здесь.

Вопрос 2)

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

person Attis    schedule 30.04.2021
comment
В вопросе нет шаблона класса. - person Daniel Langr; 30.04.2021