Search code examples
c++templatesinheritancetemplate-specializationpartial-specialization

Partial function/method template specialization workarounds


I know partial template specialization isn't supported for functions and class methods, so my question is: What are common solutions or patterns to resolve this? Below Derived derives from Base, and both of these classes have virtual methods greet() and speak(). Foo's holds a std::array<unique_ptr<T>, N> and is used in do_something(). Foo has two template parameters: T (the class type) and N (number of elements of the std::array) If N = 2, there exists a highly optimized version of do_something(). Now assume that Foo's T parameter isn't always the base class Base. Ideally, I would like to write the following code, but it's illegal:

//ILLEGAL
template<typename T>
void Foo<T,2>::do_something()
{
  arr_[0]->greet();
}

Below is the full code and my current (ugly) solution. I have to specialize do_something() twice, once for Base and once for Derived. This gets ugly if there exists multiple methods like do_something() that can be optimized on the special N=2 case, and if there exists many subclasses of 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;
}

Solution

  • There are different alternatives, depending on how other constrains in the problem one might be more appropriate than another.

    The first one is to forward the request to a static function in a template class, which allows for partial specializations:

    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();
       }
    }
    

    ;

    Then the implementation of do_something would be:

    template <typename T, int N>
    void Foo<T,N>::do_something() {
       Helper<N>::talk(*arr_[0]);
    }
    

    Alternatively, you can use tag dispatch to select one of multiple overloads:

    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>());
    }
    

    Where I have created a tag-type that can be specialized for any possible N. You could also use existing tools in C++11.

    Finally, if you need to do something like this for different functions, you can use inheritance, and push some of the functionality to a base that resolves the differences. This can be done by either pushing common code to a base, differences to an intermediate level and using a lower level front type that just inherits from the rest (base contains generic code, derived types specialize). Or alternatively with CRTP (base(s) contain differences, derived type generic code and pulls specific implementations from the bases.