Частичные функции / метод обхода специализации шаблона

Я знаю, что частичная специализация шаблонов не поддерживается для функций и методов классов, поэтому мой вопрос: каковы общие решения или шаблоны для решения этой проблемы? Ниже 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_);
}
Другие вопросы по тегам