Таким образом, у меня есть огромное количество специализаций шаблона этого шаблона:
template <typename T> // Same
struct foo { // Same
using type_name = T; // Same
foo(const int base) : _base(base) {} // May take other parameters
void func(const T& param) {} // This function signature will be the same but body will differ
int _base; // Same but may have more members
}; // Same
Таким образом, пример специализации будет:
template<>
struct foo<float> {
using type_name = T;
foo(const int base, const int child) : _base(base), _child(child) {}
void func(const T& param) { cout << param * _child << endl; }
int _base;
int _child;
};
Очевидно, это игрушечный пример и тело _func
будет более вовлеченным. Но я думаю, что это выражает идею. Я, очевидно, могу сделать макрос, чтобы помочь с образцом и поместить реализацию специализированной версии функции в файл реализации.
Но я надеялся, что C ++ предоставил мне способ сделать это без макросов. Есть ли другой способ для меня, чтобы избежать написания шаблона снова и снова?
Вы можете иметь несколько специализаций для функции, но не для всего класса
как это
#include <iostream>
#include <string>
template<typename T>
struct foo {
//common generic code
using type_name = T;
foo(const int base, const int child) : _base(base), _child(child) {}
void func(const T& param);
int _base;
int _child;
};
template<>
void foo<float>::func(const type_name&) {
//implementation
std::cout << "float" << std::endl;
}
template<>
void foo<int>::func(const type_name&) {
//implementation
std::cout << "int" << std::endl;
}int main() {
foo<int> tint(0, 0);
foo<float> fint(0, 0);
tint.func(0);
fint.func(0);
}
Вы можете использовать некоторое легкое наследование структур данных, чтобы помочь вам отделить различия в разметке элементов и определениях конструкторов от основного шаблона.
//Define an internal aggregate type you can specialize for your various template parameters
template <typename T>
struct foo_data {
foo(const int base) : _base(base) {}
int _base;
};
//Then derive privately from the data struct (or publicly if you really desire)
template <typename T>
struct foo : private foo_data<T> {
using type_name = T;
using foo_data<T>::foo_data<T>; //Make the base class constructors visible
void func(const T& param); //Use member specialization as suggested by the other answer
};
Я оставлю вам решать, лучше ли так или нет, но в итоге все общие части полностью отделены от всех необычных частей.
В комментарии под другим ответом я ошибочно назвал это CRTP. Это не так и не имеет никаких недостатков, как CRTP.
Если вам действительно нужно сохранить стандартную компоновку, то вы можете моделировать наследование вручную с явным делегированием и совершенной пересылкой.
template <typename T>
struct foo {
using type_name = T;
template <typename... Args>
foo(Args&&... args) : base_data_(std::forward<Args>(args)...) {}
void func(const T& param); //Use member specialization as suggested by the other answer
foo_data<T> base_data_;
};
Одним из недостатков является то, что я не думаю, что делегирующий конструктор будет SFINAE правильно, как написано, и это также съедает noexcept
спецификаторы и explicit
, Решение этих проблем (если необходимо) оставлено читателю в качестве упражнения.
Нет хорошего способа избежать некоторой избыточности в нотации при реализации специализаций шаблонных типов. Есть несколько методов, чтобы избежать дублирования реального кода, таких как
Использование шаблона признаков для предоставления специфичных для типа вещей
template<typename T>
struct foo_traits { ... }; // provide many specialisations
template<typename T> // no specialisations
struct foo
{
using traits = foo_traits<T>;
template<typename...Aars>
explicit foo(Args&&...args)
: data(std::forward<Args>(args)...) {}
int do_something_specific(T x)
{ return traits::do_something(data,x); }
private:
typename traits::data data;
};
очень похожий подход заключается в использовании специализированного базового класса:
template<typename T>
struct foo_base { ... }; // provide many specialisations
template<typename T> // no specialisations
struct foo : foo_base<T>
{
using base = foo_base<T>;
template<typename...Aars>
explicit foo(int m, Args&&...args)
: base(std::forward<Args>(args)...)
, more_data(m) {}
int do_something_specific(T x)
{ return base::do_something(x,more_data); }
private:
int more_data;
};
Конструктор foo
является шаблоном с переменными значениями, чтобы конструктор базового класса мог принимать любое число и тип аргументов.
Из вас можно использовать общий базовый класс и специализировать производные классы. Это можно сделать с помощью Любопытно повторяющийся шаблон (CRTP)
template<typename Derived>
struct foo_base // no specializations
{
using type = typename Derived::type;
int do_something(type x)
{
auto result = static_cast<Derived*>(this)->specific_method(x);
return do_some_common_stuff(result);
}
protected:
foo_base(type x) : data(x) {}
type data;
private:
int do_some_common_stuff(type x)
{ /* ... */ }
};
template<typename T> // some specialisations
struct foo : foo_base<foo<T>>
{
using base = foo_base<foo>;
using type = T;
using common_type = typename base::common_type;
using base::do_something;
explicit foo(type x, type y)
: base(x), extra_data(y) {}
protected:
type specific_method(type x)
{ /* ... */ }
private:
type extra_data;
};
Обратите внимание, что foo_base
это уже шаблон (в отличие от ситуации с обычным полиморфизмом), так что вы уже можете делать много специфических вещей. Только вещи, которые сделаны по-разному (не только с разными типами), нуждаются в специализации foo
,
Наконец, вы можете объединить эти подходы, например, классы признаков с CRTP.
Все эти методы реализуют некоторый тип статический или же время компиляции полиморфизм, а не реальный или динамический полиморфизм: нет виртуальных функций и, следовательно, нет виртуальной таблицы и нет накладных расходов на просмотр таблицы. Все решается во время компиляции.
Обычно это делается с помощью наследования — вы помещаете неизменную часть в базовый класс и специализируете детей.
Я не думаю, что вам нужен пример для этого, но дайте мне знать, если вы делаете.