3

Starting with the following example:

#include <cstdio>
template<int X, int Y>
struct C {
  template<int Z> void bar() {printf("Generic %d/%d\n",X,Y);}
  void foo() {bar<Y>();}
};

int
main(int argc, char *argv[])
{
  C<0,0> c0;
  c0.foo();
  C<0,1> c1;
  c1.foo();
}

I'd like to now define additional "bar()" functions specialized on the value of "Y". Specifically something like the inserted lines below (sorry I don't know how to otherwise highlight them):

#include <cstdio>
template<int X, int Y>
struct C {
  template<int Z> void bar() {printf("Generic %d/%d\n",X,Z);}
  template<> void bar<1>() {printf("Special %d/1\n",X);}
  void foo() {bar<Y>();}
};
template<int X> template<> C<X,2>::bar<2>() {printf("Special %d/2\n",X);}
int
main(int argc, char *argv[])
{
  C<0,0> c0;
  c0.foo();
  C<0,1> c1;
  c1.foo();
}

Sadly, neither of those approaches seems to be valid/compile (gcc/9.3.0, -std=c++11). ex:

tempspec.cpp:94:12: error: explicit specialization in non-namespace scope ‘struct C<X, Y>’
   94 |   template<> void bar<1>() {printf("Special %d/1\n",X);}
      |            ^
tempspec.cpp:94:26: error: template-id ‘bar<1>’ in declaration of primary template
   94 |   template<> void bar<1>() {printf("Special %d/1\n",X);}
      |                          ^
tempspec.cpp:97:33: error: expected initializer before ‘<’ token
   97 | template<int X> void C<X,2>::bar<2>() {printf("Special %d/2\n",X);}
      |                                 ^

I know I can't partially specialize a function (which is what I really want to do) but I thought here I was partially specializing the struct, and fully specializing the function.

So the question is, how do I define additional specifications of "bar()" as a member function?

(As for why, say that "bar()" is a stencil computation and "Y" is the size of the stencil. Based on "Y" I may have different implementations optimized for certain sizes.)

max66
  • 65,235
  • 10
  • 71
  • 111
justapony
  • 129
  • 8
  • I think you are misunderstanding template. Specialization of a template parameter does not mean use a value, but a type. Template allows you to define function/class for different type. They are more likely a recipe for building a user-defined type or a function. https://www.cplusplus.com/doc/oldtutorial/templates/ – Luca Jungla Nov 12 '20 at 15:29
  • 1
    @LucaJungla it is possible to provide value as template parameter. It is limited to specific simple types. https://en.cppreference.com/w/cpp/language/template_parameters – Marek R Nov 12 '20 at 15:46
  • @justapony can you provide more details (more requirements). What are you trying to achieve? Maybe there is a better solution for your problem. – Marek R Nov 12 '20 at 17:22
  • Not a whole lot of extra details. My "stencil" example generically illustrates the use case: specialized/optimized versions of member function(s). It just seems like a natural use of templates and I want to understand what I'm doing wrong syntactically and/or understand why using templates in this way is unreasonable from a C++ language point of view. I'm less interested in hacks to make it work (after all I could just write "bar" as a big case statement, #ifdef, compile time if, etc... ;^)); more interested in understanding why this isn't the "right" approach. – justapony Nov 12 '20 at 17:47
  • @justapony: I've extended my answer to try to get at the "why" a little more. Let me know if more detail would be helpful. – Vaughn Cato Nov 12 '20 at 18:12
  • So it sounds as if my error in thinking is that I thought I was partially specializing the struct (allowed), and fully specializing the function (allowed). However, the two are not separable. Not the way I was reading things, but I can see how that's the case. – justapony Nov 12 '20 at 18:39
  • Right. If you partially specialize the struct, you need a new struct definition. You can't partially specialize just one member of a struct. It is all or nothing. – Vaughn Cato Nov 12 '20 at 21:33

2 Answers2

2

As for why, say that "bar" is a stencil computation and "Y" is the size of the stencil. Based on "Y" I may have different implementations optimized for certain sizes.

So my suggestion is: avoid specialization and use overloading with tag-dispatching.

A template bar() for generic case a some non-template bar() for special cases

#include <iostream>

template <int X, int Y>
struct C
 {
   template <typename T>
   void bar (T const &)
    { std::cout << "Generic " << X << '/' << Y << '\n'; }

   void bar (std::integral_constant<int, 1> const &) 
    { std::cout << "Special " << X << '/' << 1 << '\n'; }

   void bar (std::integral_constant<int, 2> const &) 
    { std::cout << "Special " << X << '/' << 2 << '\n'; }

   void bar (std::integral_constant<int, 4> const &) 
    { std::cout << "Special " << X << '/' << 4 << '\n'; }

   void foo ()
    { bar(std::integral_constant<int, Y>{}); }
};

int main ()
 {
   C<0,0>{}.foo();
   C<0,1>{}.foo();
   C<0,2>{}.foo();
   C<0,3>{}.foo();
   C<0,4>{}.foo();
   C<0,5>{}.foo();
 }
max66
  • 65,235
  • 10
  • 71
  • 111
1

Directly doing what you want isn't allowed. Explicit specialization requires that you specify explicit values for all template parameters. This means both the class template parameters and the member function template parameters. For example, this would be legal:

template<>
template<>
void C<1,1>::bar<1>() { printf("Special %d/1\n",1); }

But if you don't use template<>, then you are either defining a previously declared member, or you are in the land of partial specialization, but partial specialization is only allowed for class templates and for variable templates due to conflicts between function overloading and partial specialization.

Note that an explicit specialization is not a template (despite the template<> syntax). Which is why is it important that it not occur inside the class template.

However, these kinds of issues can be worked around by deferring to a separate class template that you can partially specialize:

#include <cstdio>


template<int X, int Y> struct C;


template <int X, int Y, int Z>
struct CBar {
    static void bar(C<X,Y> &) {
          printf("Generic %d/%d\n",X,Z);
    }
};


template <int X, int Y>
struct CBar<X,Y,1> {
    static void bar(C<X,Y> &) {
          printf("Special %d/%d\n",X,1);
    }
};


template <int X>
struct CBar<X,2,2> {
    static void bar(C<X,2> &) {
          printf("Special %d/%d\n",X,2);
    }
};


template<int X, int Y>
struct C {
    template<int Z> void bar() { CBar<X,Y,Z>::bar(*this); }
    void foo() {bar<Y>();}
};


int main(int , char *[])
{
    C<0,0> c0;
    c0.foo();
    C<0,1> c1;
    c1.foo();
    C<0,2> c2;
    c2.foo();
}

Program stdout

Generic 0/0
Special 0/1
Special 0/2
Vaughn Cato
  • 63,448
  • 5
  • 82
  • 132
  • You've changed problem to solve. He has template method in template class and you transformed problem to class template. Also he needs full specialization of method of partially specialized class. Here is similar problem https://stackoverflow.com/a/4995094/1387438 which was solved. – Marek R Nov 12 '20 at 15:49
  • @MarekR: I don't see what you are saying. I am solving the problem using another class template, but I don't see how that is changing the problem. – Vaughn Cato Nov 12 '20 at 15:57
  • @MarekR: I do see that I specialized the wrong thing though. I'll fix that. – Vaughn Cato Nov 12 '20 at 16:00
  • @MarekR: Actually, it seems like the intent of the OP was to specialize `foo`, and deferring to `bar` was an attempt at that. – Vaughn Cato Nov 12 '20 at 16:03