Шаблонная специализация одного метода из шаблонного класса

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

template <class T>
class TClass 
{
public:
 void doSomething(std::vector<T> * v);
};

template <class T>
void TClass<T>::doSomething(std::vector<T> * v) {
 // Do somtehing with a vector of a generic T
}

template <>
inline void TClass<int>::doSomething(std::vector<int> * v) {
 // Do somtehing with a vector of int's
}

Но обратите внимание на inline в методе специализации. Требуется, чтобы код не имел ошибки компоновщика (в VS2008 есть LNK2005) из-за того, что метод определен более одного раза. Я понимаю это, потому что AFAIK полная спецификация шаблона совпадает с простым определением метода.

Итак, как мне удалить этот inline? Код не должен дублироваться при каждом его использовании. Я искал Google, читал некоторые вопросы здесь, в SO и пробовал многие из предлагаемых решений, но ни один из них не был успешно построен (по крайней мере, не в VS 2008).

Спасибо!

Ответ 1

Как и в простых функциях, вы можете использовать объявление и реализацию. Поместите объявление заголовка:

template <>
void TClass<int>::doSomething(std::vector<int> * v);

и поместите реализацию в один из ваших cpp файлов:

template <>
void TClass<int>::doSomething(std::vector<int> * v) {
 // Do somtehing with a vector of int's
}

Не забудьте удалить встроенный (я забыл и думал, что это решение не сработает:)). Проверено на VС++ 2005

Ответ 2

Вам нужно перенести определение специализации в файл CPP. Специализация функции-члена класса шаблона допускается, даже если функция не объявлена ​​как шаблон.

Ответ 3

Нет причин для удаления ключевого слова inline.
В любом случае это не изменяет значения кода.

Ответ 4

Если вы хотите удалить встроенную строку по какой-либо причине, то решение maxim1000 будет абсолютно корректным.

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

Цитата из Часто задаваемые вопросы по С++

Существует несколько способов указать, что функция является встроенной, некоторые из которые включают ключевое слово inline, другие - нет. Независимо от того, как вы назначить функцию как встроенную, это запрос, который компилятор разрешено игнорировать: компилятор может встроить-расширять некоторые, все или ничто мест, где вы вызываете функцию, обозначенную как встроенная. (Dont не разочаруйтесь, если это кажется безнадежно неопределенным. Гибкость выше - на самом деле огромное преимущество: он позволяет компилятору обрабатывать большие функции отличаются от небольших, плюс он позволяет компилятору генерировать код, который легко отлаживать, если вы выберете правильный компилятор варианты.)

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

Ответ 5

Это небольшая ОТ, но я решил оставить это здесь на случай, если это поможет кому-то еще. Я гуглил о специализации шаблонов, которая привела меня сюда, и хотя ответ @maxim1000 был правильным и в конечном итоге помог мне разобраться в моих проблемах, я не думал, что он был совершенно ясен.

Моя ситуация немного отличается (но достаточно похожа, чтобы оставить этот ответ, я думаю), чем ОП. По сути, я использую стороннюю библиотеку со всеми видами классов, которые определяют "типы статуса". Сердцем этих типов являются просто enum, но все классы наследуются от общего (абстрактного) родителя и предоставляют различные вспомогательные функции, такие как перегрузка операторов и static toString(enum type) функция static toString(enum type). Каждое enum статуса отличается друг от друга и не связано. Например, у одного enum есть поля NORMAL, DEGRADED, INOPERABLE другого - AVAILBLE, PENDING, MISSING и т.д. Мое программное обеспечение отвечает за управление различными типами статусов для разных компонентов. Это произошло из-за того, что я хотел использовать функции toString для этих enum классов, но, поскольку они абстрактные, я не смог создать их экземпляр напрямую. Я мог бы расширить каждый класс я хотел использовать, но в конце концов я решил создать template класс, где typename бы то, что конкретного статус enum я заботился. Вероятно, могут возникнуть некоторые споры по поводу этого решения, но я чувствовал, что это была намного меньшая работа, чем расширение каждого абстрактного класса enum собственным и реализация абстрактных функций. И, конечно же, в моем коде я просто хотел иметь возможность вызвать .toString(enum type) и заставить его печатать строковое представление этого enum. Поскольку все enum были совершенно не связаны, у каждой из них были свои собственные функции toString которые (после некоторых исследований, которые я изучил) должны были вызываться с использованием специализации шаблонов. Это привело меня сюда. Ниже MCVE того, что я должен был сделать, чтобы сделать эту работу правильно. И на самом деле мое решение немного отличалось от @maxim1000.

Это (очень упрощенный) заголовочный файл для enum. В действительности каждый класс enum был определен в своем собственном файле. Этот файл представляет заголовочные файлы, которые предоставляются мне как часть библиотеки, которую я использую:

// file enums.h
#include <string>

class Enum1
{
public:
  enum EnumerationItem
  {
    BEARS1,
    BEARS2,
    BEARS3
  };

  static std::string toString(EnumerationItem e)
  {
    // code for converting e to its string representation,
    // omitted for brevity
  }
};

class Enum2
{
public:
  enum EnumerationItem
  {
    TIGERS1,
    TIGERS2,
    TIGERS3
  };

  static std::string toString(EnumerationItem e)
  {
    // code for converting e to its string representation,
    // omitted for brevity
  }
};

добавление этой строки просто для разделения следующего файла на другой блок кода:

// file TemplateExample.h
#include <string>

template <typename T>
class TemplateExample
{
public:
  TemplateExample(T t);
  virtual ~TemplateExample();

  // this is the function I was most concerned about. Unlike @maxim1000's
  // answer where (s)he declared it outside the class with full template
  // parameters, I was able to keep mine declared in the class just like
  // this
  std::string toString();

private:
  T type_;
};

template <typename T>
TemplateExample<T>::TemplateExample(T t)
  : type_(t)
{

}

template <typename T>
TemplateExample<T>::~TemplateExample()
{

}

следующий файл

// file TemplateExample.cpp
#include <string>

#include "enums.h"
#include "TemplateExample.h"

// for each enum type, I specify a different toString method, and the
// correct one gets called when I call it on that type.
template <>
std::string TemplateExample<Enum1::EnumerationItem>::toString()
{
  return Enum1::toString(type_);
}

template <>
std::string TemplateExample<Enum2::EnumerationItem>::toString()
{
  return Enum2::toString(type_);
}

следующий файл

// and finally, main.cpp
#include <iostream>
#include "TemplateExample.h"
#include "enums.h"

int main()
{
  TemplateExample<Enum1::EnumerationItem> t1(Enum1::EnumerationItem::BEARS1);
  TemplateExample<Enum2::EnumerationItem> t2(Enum2::EnumerationItem::TIGERS3);

  std::cout << t1.toString() << std::endl;
  std::cout << t2.toString() << std::endl;

  return 0;
}

и это выводит:

BEARS1
TIGERS3

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