Шаблон функции в пространстве имен в отдельном файле компилируется нормально, но компоновщик не может его найти - PullRequest
7 голосов
/ 16 июля 2010

Эта проблема заключается в определении и объявлении шаблона функции в пространстве имен, которое определено во внешнем файле, из которого создается функция. Вот самый маленький воспроизводимый пример, который я мог придумать. Далее следуют 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, я получаю ту же ошибку.

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

Ответы [ 4 ]

10 голосов
/ 16 июля 2010

Вы пытаетесь скрыть реализацию вашей шаблонной функции в файле cpp, что, к сожалению, невозможно для большинства компиляторов.Шаблонные функции / классы создаются при использовании, поэтому в точке, где вы вызываете DoSomething, компилятору требуется определение функции, чтобы иметь возможность ее скомпилировать.

Существует несколько решений.

  1. Переместить тело функции в файл заголовка.У вас были проблемы с этим раньше, но я бы сказал, что это связано с чем-то другим.Это предпочтительный подход.

  2. Включите файл cpp из foo.cpp.(дикий, но не , что редко).

  3. Создание шаблона для 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
1 голос
/ 16 июля 2010

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

  1. Определение шаблона
  2. Параметры шаблона для создания экземпляра с

В вашем случае компилятор работает с двумя модулями компиляции. Он вполне может запускать их в двух разных процессах, поэтому блоки компиляции не зависят друг от друга. Когда компилятор компилирует bar.cpp, он видит определение шаблона без запросов (вызовов) для конкретных экземпляров. Следовательно, компилятор не создает экземпляр шаблона; компиляция bar.cpp фактически ничего не дает. Компилятор достаточно «умен», чтобы увидеть, что вам не нужен этот шаблон, и оптимизирует его до нуля.

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

Наконец, компоновщик приходит и получает окончательный вид с высоты птичьего полета. Он может видеть, что для std::vector<double>::iterator нет экземпляра DoSomething<Iter>(Iter, Iter), и жалуется.

Есть несколько решений вашей проблемы. Лучшее решение - использовать ключевое слово export при объявлении шаблона. К сожалению, это также худшее решение, поскольку подавляющее большинство компиляторов не поддерживают эту стандартную функцию.

Если серьезно, то лучше всего определить шаблон в заголовочном файле. Не объявляйте это там, определите это там. Вам не понадобится bar.cpp, если это именно то, для чего он был нужен.

1 голос
/ 16 июля 2010

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

1 голос
/ 16 июля 2010

Помещение определений шаблонов в отдельный исходный файл не очень хорошо поддерживается (я считаю, что единственный компилятор, поддерживающий 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
...