Частичные функции / метод обхода специализации шаблона
Я знаю, что частичная специализация шаблонов не поддерживается для функций и методов классов, поэтому мой вопрос: каковы общие решения или шаблоны для решения этой проблемы? Ниже Derived
происходит от Base
и оба этих класса имеют виртуальные методы greet()
а также speak()
, Foo
удерживает std::array<unique_ptr<T>, N>
и используется в do_something()
, Foo
имеет два параметра шаблона: T
(тип класса) и N
(количество элементов std::array
) Если N
= 2, существует высокооптимизированная версия do_something()
, Теперь предположим, что Foo
"s T
параметр не всегда базовый класс Base
, В идеале я хотел бы написать следующий код, но это незаконно:
//ILLEGAL
template<typename T>
void Foo<T,2>::do_something()
{
arr_[0]->greet();
}
Ниже приведен полный код и мое текущее (безобразное) решение. Я должен специализироваться do_something()
дважды, один раз для Base
и один раз для Derived
, Это становится ужасно, если существует несколько методов, таких как do_something()
которые могут быть оптимизированы на специальном N
= 2, и если существует много подклассов Base
,
#include <iostream>
#include <memory>
class Base
{
public:
virtual void speak()
{
std::cout << "base is speaking" << std::endl;
}
virtual void greet()
{
std::cout << "base is greeting" << std::endl;
}
};
class Derived : public Base
{
public:
void speak()
{
std::cout << "derived is speaking" << std::endl;
}
void greet()
{
std::cout << "derived is greeting" << std::endl;
}
};
template<typename T, int N>
class Foo
{
public:
Foo(std::array<std::unique_ptr<T>, N>&& arr) :
arr_(std::move(arr))
{
}
void do_something();
std::array<std::unique_ptr<T>, N> arr_;
};
template<typename T, int N>
void Foo<T,N>::do_something()
{
arr_[0]->speak();
}
//Want to avoid "copy-and_paste" of do_something() below
template<>
void Foo<Base,2>::do_something()
{
arr_[0]->greet();
}
template<>
void Foo<Derived,2>::do_something()
{
arr_[0]->greet();
}
int main()
{
constexpr int N = 2;
std::array<std::unique_ptr<Derived>, N> arr =
{
std::unique_ptr<Derived>(new Derived),
std::unique_ptr<Derived>(new Derived)
};
Foo<Derived, N> foo(std::move(arr));
foo.do_something();
return 0;
}
2 ответа
Существуют разные альтернативы, в зависимости от того, как другие ограничения в проблеме могут быть более подходящими, чем другие.
Первый - перенаправить запрос статической функции в шаблонном классе, которая допускает частичные специализации:
template <int N>
struct Helper {
template <typename T>
static void talk(T& t) { // Should be T const &, but that requires const members
t.speak();
}
};
template <>
struct Helper<2> {
template <typename T>
static void talk(T& t) {
t.greet();
}
}
;
Тогда реализация do_something
было бы:
template <typename T, int N>
void Foo<T,N>::do_something() {
Helper<N>::talk(*arr_[0]);
}
В качестве альтернативы вы можете использовать диспетчеризацию тегов для выбора одной из нескольких перегрузок:
template <int N> struct tag {};
template <typename T, int N>
template <int M>
void Foo<T,N>::do_something_impl(tag<M>) {
arr_[0]->speak();
}
template <typename T, int N>
void Foo<T,N>::do_something_impl(tag<2>) {
arr_[0]->greet();
}
template <typename T, int N>
void Foo<T,N>::do_something() {
do_something_impl(tag<N>());
}
Где я создал тип тега, который может быть специализирован для любого возможного N
, Вы также можете использовать существующие инструменты в C++11.
Наконец, если вам нужно сделать что-то подобное для разных функций, вы можете использовать наследование и перенести некоторые функциональные возможности в базу, которая устраняет различия. Это может быть сделано путем передачи общего кода на базу, различий на промежуточный уровень и использования типа фронта более низкого уровня, который просто наследуется от остальных (база содержит общий код, специализированные производные типы). Или, в качестве альтернативы, с CRTP (база (-ы) содержат различия, общий код производного типа и извлекает конкретные реализации из баз.
Хитрость заключается в том, чтобы перенаправить реализацию на вспомогательный класс шаблона и частично специализировать этот класс и / или использовать диспетчеризацию тегов:
namespace {
template<typename T, int N, bool isBase = std::is_base_of<Base, T>::value>
struct helper {
// general case:
void operator () (std::array<std::unique_ptr<T>, N>& arr_) const
{
arr_[0]->speak();
}
};
template<typename T>
struct helper<T, 2, true>
{
void operator () (std::array<std::unique_ptr<T>, 2>& arr_) const
{
arr_[0]->greet();
}
};
// You may add other specialization if required.
}
template<typename T, int N>
void Foo<T,N>::do_something()
{
helper<T, N>()(arr_);
}