4

I'm setting up a c++(11) program in which I use a templated class that depends on 2 parameters. Much of the class can be written generically upon template parameters. Only few functions need specialized version. Here is an example pattern that reproduces my problem :

template<class T, int N> 
class foo
{
  // typedefs and members that depend on T and N
  // but that can be written generically e.g. :
  typedef std::array<T,N> myarray;
  void myfunc(myarray tab);
};

// ...

template<class T, int N>
foo<T,N>::myfunc(myarray tab)
{
  // generic version
}

// need specialization only of myfunc:
template<class T>
foo<T,1>::myfunc(myarray tab)
{
  // specialized version for N=1
}

then compiler complains : error: invalid use of incomplete type ‘class foo<T, 1>’ about then line template<class T> foo<T,1>::myfunc(myarray tab)

The only workaround I found to work was to insert a complete duplicate of the class with its specialized version :

template<class T>
class foo<T,1>
{
  // recopy all the lines of class foo<T,N>, replacing N by 1
};
// duplicate as well all generic function definition with 
// specialized versions <T,1> even when not needed

what is very unsatisfactory ...

After some experiments, I found out that this problem does not seem to occur when template uses only 1 parameter (eg template <int N> class foo{...};) but only when at least 2 parameters are involved.

Is this something well known in C++ programming ? Is there some smarter method to solve my problem ? (I thought of creating a mother class without the specialized functions and then make class foo inherit from it, keeping in it only specialized members, in order to minimize the "duplication workaround")

Thanks for advices !

Mathias G
  • 43
  • 4

3 Answers3

3

Tag dispatch to the rescue.

Using std::integral_constant, we can create two types, one for a generic N, and one for 1 (or you could define some other type template on an int, but I chose to use something that was already in existence).

void myfunc(myarray tab)
{
    myfunchelper(tab, 
       typename std::is_same<std::integral_constant<int, N>,
                             std::integral_constant<int, 1>>::type{});
}

Which dispatches the call to a helper function overloaded for std::true_type (scenario where N==1) and std::false_type (scenario where N != 1):

void myfunchelper(myarray tab, std::false_type);
void myfunchelper(myarray tab, std::true_type);

Note I purposely don't name the type because it's not used, and a smart compiler will optimize away any kind of allocation for that type (I think).

Live Demo

(code from demo below):

#include <array>
#include <iostream>
#include <type_traits>

template<class T, int N> 
class foo
{
public:
  // typedefs and members that depend on T and N
  // but that can be written generically e.g. :
  typedef std::array<T,N> myarray;
  void myfunc(myarray tab)
  {
      myfunchelper(tab, typename std::is_same<std::integral_constant<int, N>, std::integral_constant<int, 1>>::type{});
  }
  
private:
  void myfunchelper(myarray tab, std::false_type)
  {
      std::cout << "Generic myfunc\n";
  }
  void myfunchelper(myarray tab, std::true_type)
  {
      std::cout << "myfunc specialized for N==1\n";
  }
};

int main()
{
    std::array<char, 1> arr1{{'c'}};
    std::array<double, 2> arr2{{1.0, 2.0}};
    foo<char, 1> f1;
    foo<double, 2> f2;
    f1.myfunc(arr1); // calls specialized version
    f2.myfunc(arr2); // calls generic version
}

In regards to the solution you posted, which is also a tag-dispatching approach, it involves defining another int-template class, which introduces an unnecessary type into the surrounding scope, and also involves casting a and NULL (well, 0 in this case, which is what NULL is usually typedef'd as).

Effectively, the solution I've posted is the same, but I think it to be a little more clearer, and a little more type safe.

Community
  • 1
  • 1
AndyG
  • 39,700
  • 8
  • 109
  • 143
  • Thank you for your response. Indeed, it is cleaner and more straightforward than what I proposed. – Mathias G Apr 08 '16 at 19:08
  • Becomes more and more clumsy with every new specialization you need to add. – SergeyA Apr 08 '16 at 19:10
  • @SergeyA. Agreed, but for small cases like this, tag dispatch sure beats the amount of copy-paste you suffer when you need to partially specialize an entire class. – AndyG Apr 08 '16 at 19:54
  • @AndyG, agreed as well - this is why I prefer parent class approach :) – SergeyA Apr 08 '16 at 19:56
2

Short answer: you can fully specialize members in such way, but you can't partially specialize them.

Long answer: When there is only one template argument to your class, for example:

template <class T> struct X {
   void foo() { };
}

template <> void X<int>::foo() {  }

is full specialization, and it is allowed. However,

template <class T, class Y> struct X {
   void foo() { };
}

template <class T> void X<int, Y>::foo() {  }

Is partial specialization, and partial specialization of a single member is not allowed - you need to partially specialize the whole class.

SergeyA
  • 61,605
  • 5
  • 78
  • 137
  • Thank you for pointing out the C++ rule. Now, is there a recommended implementation that avoids unnecessary code duplication ? – Mathias G Apr 08 '16 at 16:06
  • 1
    @MathiasG, you already mentioned it yourself. Create a base class with functions which are the same, have a class which publicly derives from it with methods which should be specialized, and have a partial specialization of the derived whole class for your special types. – SergeyA Apr 08 '16 at 16:11
0

Googling to Specialization of templated member function in templated class and adapting Matthieu M.'sanswer, I think I found a solution (overloading) to my problem :

template<int N> class virtualclass{};

template<class T, int N> 
class foo
{
  // typedefs and members that depend on T and N
  // but that can be written generically e.g. :
  typedef std::array<T,N> myarray;
  void myfunc(myarray tab)
  {
     helperfunc((virtualclass<N>*)0, tab);
  }

  template<class P>
  void helperfunc(P*, myarray tab);

  void helperfunc(virtualclass<1>*, myarray tab);
};

// ...

template<class T, int N>
template<class P>
foo<T,N>::helperfunc(P*,myarray tab)
{
  // generic version
}

// need specialization only of myfunc:
template<class T, int N>
foo<T,N>::helperfunc(virtualclass<1>*, myarray tab)
{
  // specialized version for N=1
}

Does it look like good practice ? May there be some hidden drawbacks with this solution ?

Community
  • 1
  • 1
Mathias G
  • 43
  • 4