Оптимизация компилятора удаляет неявное создание экземпляров шаблона, что приводит к ошибкам компоновщика.

Я пытаюсь переместить определение функции шаблона из файла заголовка.

Я знаю, что для этого я могу явно создать экземпляр этой функции со всеми типами, которые я хочу видеть извне (во время компоновки). Однако внутри исходного файла (вместе с определением функции) у меня есть использование (неявное создание) функции со всеми необходимыми типами. Этот код работает и отлично линкуется с -O0, но не линкуется с -O2.

Я смог минимизировать проблему:

// a.cpp
#include "b.h"

int main() {
  bar<int>();
  return 0;
}
// b.h
template <class T> void bar();
// b.cpp
#include "b.h"

template <class T> void bar() {}

void foo() { bar<int>(); }

Если я скомпилирую b.cpp с параметрами -O0 и -O2, я получу другой набор символов, доступных в объектном файле:

> clang++ -c b.cpp -o b.o && nm -A b.o

b.o:0000000000000000 W _Z3barIiEvv
b.o:0000000000000000 T _Z3foov

> clang++ -c b.cpp -O2 -o b.o && nm -A b.o

b.o:0000000000000000 T _Z3foov

То же самое происходит и с gcc.

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

Можно ли предотвратить удаление неявно созданных экземпляров функций шаблона без их явного создания?

Заранее спасибо!

UPD: этот ответ на связанный вопрос (как указал @Jarod42) отвечает на мой вопрос а также: по стандарту это невозможно сделать без явного инстанцирования.


person Valeriy Savchenko    schedule 31.05.2019    source источник
comment
Код, который вы показали, только пытается ВЫЗВАТЬ экземпляр шаблона. Нет ни кода, который фактически определяет шаблонную функцию, ни кода, который явно создает экземпляр шаблона таким образом, чтобы его можно было разрешить в точке вызова (т. е. гарантировать наличие функции bar<int>(), которую можно вызвать из main()). Такой код нужен в вашем проекте, если вы хотите, чтобы программа линковалась. Если код работает без оптимизации, это ошибка компилятора или компоновщика.   -  person Peter    schedule 31.05.2019
comment
Прежде всего, спасибо за быстрый ответ! В b.cpp есть определение bar. foo вызывает bar<int>, и поскольку у нас есть определение, мы должны сгенерировать тело для bar<int>, что и делает компилятор. После этого он встраивает свое тело в foo и удаляет его. Я не совсем понимаю понятие «попытка вызова», поскольку компоновщик ошибается только в функции main, а не в функции foo.   -  person Valeriy Savchenko    schedule 31.05.2019
comment
Определение в bar.cpp является локальным для этого файла. Его нельзя вызвать из main().   -  person Peter    schedule 31.05.2019
comment
@peter компилирует/связывает/запускает без оптимизации. При явном создании экземпляра (добавление template void bar<int>();) в bar.cpp он компилируется/связывается/запускается с оптимизацией и без нее, даже если определение является локальным для b.cpp.   -  person Valeriy Savchenko    schedule 31.05.2019
comment
Связанный/дубликат linker-error- with-implicit-instantition-of-private-c-template-with-llvm-clang   -  person Jarod42    schedule 31.05.2019
comment
Да, я прочитал вопрос. Проблема в том, что стандарт не требует, чтобы экземпляр bar<int> можно было вызывать из main(). Как ни странно, то, как ваша программа строится без оптимизации, на самом деле не требуется стандартом C++, и неудача с оптимизацией ближе к правильному. Кроме того, bar<int> на самом деле неявно создается в bar.cpp, а не явно. Явное создание экземпляра отличается от того, что происходит из-за вызова bar<int> в bar.cpp. В любом случае, чтобы найти решение вашей проблемы, поищите внешние шаблоны.   -  person Peter    schedule 31.05.2019
comment
@peter Я ожидал, что это не проблема компиляторов и что это стандартное поведение. Я надеялся, что есть обходной путь, чтобы сделать это без явного создания экземпляра (на котором я настаиваю template void bar<int>();). Как указал @Jarod42, есть аналогичный вопрос, и есть ответ, цитирующий стандарт, что обходного пути нет.   -  person Valeriy Savchenko    schedule 31.05.2019
comment
Если вы хотите сделать это без явного создания экземпляра, вам нужно вернуться к неявному созданию экземпляра. Ограничение неявного создания экземпляров заключается в том, что они обычно являются локальными для модуля компиляции, где они создаются, что означает, что определение шаблонной функции должно быть видимым в точке вызова. Есть некоторые специфичные для компилятора (или инструментальные) расширения, чтобы обойти эти ограничения. Использование таких подходов привязывает вас к конкретному компилятору и (в некоторых случаях) к конкретной версии компилятора. Это нормально, если вы счастливы быть запертыми в использовании одного компилятора.   -  person Peter    schedule 01.06.2019