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

92

Всегда учитывая, что следующий заголовок, содержащий мой шаблонный класс, включен как минимум в два .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 something with a vector of a generic T
}

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

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

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

Благодарность!

Чуим
источник
4
Почему вы хотите удалить встроенный? Вам это не нравится с эстетической точки зрения? Как вы думаете, это меняет смысл вашего кода?
Мартин Йорк
1
Потому что, если бы этот метод был «длинным» и использовался бы во многих местах, я бы везде копировал его двоичный код, верно? Я попытался объяснить это в вопросе, но, думаю, было непонятно ... :)
Chuim
@Martin: Что делать, если реализации требуется много другого кода, который затем должен быть включен в этот заголовок вместо файла cpp?
sbi

Ответы:

72

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

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
}

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

maxim1000
источник
Я пробовал что-то вроде этого раньше, но у меня были другие ошибки, но теперь, когда вы упомянули, я, должно быть, забыл удалить во inlineвремя копирования / вставки. Вот так это сработало!
Chuim
То же самое относится к функциям без шаблонов (в отличие от методов класса). Я получал ту же ошибку компоновщика для моей специализации функции. Я переместил тело специализации функции в файл .cpp и оставил объявление специализации в заголовке, и все заработало. Благодарность!
aldo
Я только что столкнулся с этой проблемой, и все это решило за меня. Кроме того, вам нужно позаботиться о том, где компилятор расширяет код шаблона. Если это делается дважды, компилятор жалуется на несколько определений.
Diederik
4

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

БостонЛоган
источник
3

Нет причин удалять ключевое слово inline.
Это никак не меняет смысла кода.

Мартин Йорк
источник
Скопировано из комментария к вопросу: Потому что, если бы этот метод был "длинным" и использовался бы во многих местах, я бы скопировал его двоичный код везде, верно? Я попытался объяснить это в вопросе, но, думаю, было непонятно ... :)
Chuim
1
Нет. Компоновщик удаляет все лишние копии. Таким образом, в приложении или библиотеке у вас будет только один экземпляр метода.
Мартин Йорк
3
Если inlineключевое слово приводит к тому, что функция фактически встроена (в стандарте сказано, что компилятор должен воспринимать это как подсказку), то эти лишние копии не могут быть удалены. Однако это всего лишь намек на встраивание (его основной эффект - сказать «не генерировать ошибки при конфликтах ссылок определенным образом»)
Якк - Адам Неврамонт
2

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

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

Цитата из C ++ FAQ

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

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

Трискельдейский
источник
1

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

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

Ссылка: https://stackoverflow.com/a/4445772/1294184

Иордания
источник
0

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

Моя ситуация немного отличается (но достаточно похожа, чтобы оставить этот ответ, я думаю), чем у OP. По сути, я использую стороннюю библиотеку со всеми видами классов, которые определяют «типы статуса». Сердцем этих типов являются просто enums, но все классы наследуются от общего (абстрактного) родителя и предоставляют различные служебные функции, такие как перегрузка оператора и 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.

Это (значительно упрощенный) заголовочный файл для enums. На самом деле каждый 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класс, который я хочу использовать.

Яно
источник