Эта проблема находится в определении и объявлении шаблона функции в пространстве имен, которое определяется во внешнем файле от того, где функция инстанцируют. Вот самый маленький восстанавливаемый пример, который я мог придумать. 4 файла следуют:
Объявление шаблона функции в именованном пространстве имен:
// bar.h
#include <algorithm>
namespace barspace {
template <typename Iter>
void DoSomething (Iter first, Iter last);
}
Определение шаблона функции в отдельном файле:
// bar.cpp
#include "bar.h"
namespace barspace {
template <typename Iter>
void DoSomething (Iter first, Iter last) {
typedef typename std::iterator_traits<Iter>::value_type val_t;
std::sort (first, last);
}
} // namespace barspace
Заголовок для основной программы
// foo.h
#include "bar.h"
#include <vector>
Наконец, основная программа, где шаблон функции называют:
//foo.cpp
#include "foo.h"
int main () {
std::vector<double> v_d;
for (int i = 0; i < 10; i++) {
v_d.push_back (i);
}
barspace::DoSomething (v_d.begin(), v_d.end());
return 0;
}
Я компилирую следующим образом:
g++ -c -o bar.o bar.cpp
g++ -c -o foo.o foo.cpp
Они хорошо работают. Теперь для соединения:
g++ bar.o foo.o -o foobar
И получающаяся ошибка компилятора о неопределенной ссылке:
foo.o: In function `main':
foo.cpp:(.text+0x6e): undefined reference to `void barspace::DoSomething<__gnu_cxx::__normal_iterator<double*, std::vector<double, std::allocator<double> > > >(__gnu_cxx::__normal_iterator<double*, std::vector<double, std::allocator<double> > >, __gnu_cxx::__normal_iterator<double*, std::vector<double, std::allocator<double> > >)'
collect2: ld returned 1 exit status
Существует очевидная проблема с кодом, не сделанным доступный из namespace
, или от bar
единица компиляции.
Кроме того, когда я пытаюсь поместить определение DoSomething
в bar.h
заголовок, как я был бы для хитрости проблем при определении методов шаблона класса в отдельных .cpp файлах, я получаю ту же ошибку.
Можно ли пролить некоторый свет к моему компилятору, связывающему ошибку?
Вы пытаетесь спрятать реализацию вашей шаблонизированной функции в cpp-файл, что, к сожалению, невозможно для большинства компиляторов. Шаблонные функции/классы инстанцируются при использовании, поэтому в тот момент, когда вы вызываете DoSomething
, компилятору необходимо определение функции, чтобы скомпилировать ее.
Есть несколько решений.
Перенесите тело функции в заголовочный файл. Раньше у вас были проблемы с этим, но я бы сказал, что это связано с чем-то другим. Это предпочтительный подход.
Включите cpp-файл из foo.cpp
. (дикий, но не такой необычный).
Инстанцируйте шаблон для double
:
// bar.cpp
#include "bar.h"
namespace barspace {
template<>
void DoSomething<double> (double first, double last) {
typedef typename std::iterator_traits<double>::value_type val_t;
std::sort (first, last);
}
} // namespace barspace
Помещение определений шаблонов в отдельный исходный файл не очень хорошо поддерживается (я считаю, что единственный компилятор, который поддерживает такую поддержку, - это Comeau).
Вам нужно переместить определение в файлы заголовков:
// bar.h
namespace barspace {
template <typename Iter>
void DoSomething (Iter first, Iter last) {
typedef typename std::iterator_traits<Iter>::value_type val_t;
std::sort (first, last);
}
} // namespace barspace
Многим компиляторам не нравится, когда функции шаблонов определены в отдельных файлах. Поместите все определение в файл заголовка, и оно должно нормально скомпилироваться.
Вы должны определить функции шаблона в заголовках. Вы не можете объявить их, а затем определить в файле реализации; это просто не сработает. Причина в том, что компилятор должен создать экземпляры шаблонов, прежде чем вы сможете их вызвать. Чтобы создать экземпляр шаблона функции, компилятору нужны две вещи:
В вашем случае компилятор работает с двумя модулями компиляции. Он вполне может запускать их в двух разных процессах, поэтому единицы компиляции независимы друг от друга. Когда компилятор компилирует bar.cpp, он видит определение шаблона без запросов (вызовов) для конкретных экземпляров. Следовательно, компилятор не создает экземпляр шаблона; компиляция bar.cpp фактически ничего не дает.Компилятор достаточно «умен», чтобы видеть, что вам не нужен этот шаблон, и оптимизирует его до нулевых накладных расходов.
Конечно, вам нужен этот шаблон - в foo.cpp - но это другая единица компиляции, и к настоящему времени компилятор забыл все (или еще не узнал) о bar.cpp. Однако компилятор видит объявление функции-шаблона и делает обычное предположение, что если она была объявлена, то она была определена (инстанцирована) где-то еще - и ничего не говорит.
Наконец, появляется компоновщик и получает полную картину с высоты птичьего полета. Он видит, что для std :: vector
не создан экземпляр DoSomething
, и жалуется.
Есть несколько решений вашей проблемы. Лучшее решение - использовать ключевое слово export
при объявлении шаблона. К сожалению, это также наихудшее решение, поскольку подавляющее большинство компиляторов не поддерживают эту стандартную функцию.
Если серьезно, лучше всего определить шаблон в файле заголовка. Не объявляйте это там, определите там. Вам не понадобится bar.cpp, если он изначально был для этого предназначен.