1

I'm trying to wrap my head around the order of instantiation and specialization of templates, especially in cases where subclasses are involved.

Please consider the following example:

class A {
  int value() { return 42; }
};
class B {
  double value() { return 3.1415; }
};

class MyBase {
public:
  virtual int value() = 0;
};

template<class T> class MyClass : public MyBase {
protected:
  class Cache;
  mutable Cache _cache;
public:
  MyClass(T t) : _cache(t.value()) {};
  virtual int value() override;
};

template<class T> class MyClass<T>::Cache {
public:
  int x;
  int getVal(){return x;}
};

template<> class MyClass<B>::Cache {
public:
  double z;
  int getVal(){return 100*z;}  
};

template<class T> int MyClass<T>::value() { return _cache.getVal(); }

typedef MyClass<A> MyA;
typedef MyClass<B> MyB;    

This results in error: specialization of ‘MyClass<B>::Cache’ after instantiation, which I don't understand. I would have guessed that MyClass<B>::Cache is only required once the typedef happens, but that doesn't seem to be the case. I have tried to move around the specialization a bit, before the general template definition, inside the definition of MyClass or even before that (using forward declares), but without any luck.

Is there a proper way to achieve something like this, or is this just logically impossible for reasons that I would be delighted to understand better?

I guess I could achieve this by making _cache a pointer such that a full definition is not required at time of defining MyClass, but I'd rather avoid doing that unless I understand the underlying issue.

carsten
  • 1,315
  • 2
  • 13
  • 27
  • 1
    The issue is that the `MyClass` in `template<> class MyClass::Cache` implicity instantiates `MyClass`, which has the member `Cache _cache;` that implicitly instantiates `Cache` (with the previous definition) – Artyer Sep 19 '20 at 16:38

1 Answers1

1

Miscellaneous reminder for the reader:

  1. We cannot have specialization at class scope : demo
  2. We cannot forward declare an inner class.
  3. We can fully specialize en inner class (see cppref 6.)

First Solution :

We specialize the whole class, so we can have a specialize inner class. Live demo

#include <iostream>
#include <stdexcept>

struct A {
  int value() { return 42; }
};
struct B {
  double value() { return 3.1415; }
};

class MyBase {
public:
  virtual int value() = 0;
};

template <class T> class MyClass : public MyBase {
protected:
  struct Cache {
    int x;
    int getVal() { return x; }
    Cache(double v) : x(v){}; // gcc11 need this in c++17, not in c++20
  };

public:
  mutable Cache _cache;

public:
  MyClass(T t) : _cache(t.value()){};
  virtual int value() override;
};

template <> class MyClass<B> : public MyBase {
protected:
  struct Cache {

    double z;
    Cache(double v) : z(v){}; // gcc11 need this in c++17, not in c++20
    int getVal() { return 100 * z; }
  };

  mutable Cache _cache;

public:
  MyClass(B t) : _cache(t.value()){};
  virtual int value() override;
};

template <class T> int MyClass<T>::value() { return _cache.getVal(); }
int MyClass<B>::value() { return _cache.getVal(); }

typedef MyClass<A> MyA;
typedef MyClass<B> MyB;

int main() {
  MyA a(A{});
  MyB b(B{});

  std::cout << a.value() << " " << b.value() << std::endl;
}

Second solution :

Make Cache template and outside of MyClass so we can specialize. IMHO it's cleaner. Live demo.

#include <iostream>
#include <stdexcept>

struct A {
  int value() { return 42; }
};
struct B {
  double value() { return 3.1415; }
};

template <class T> struct Cache {
  int x;
  int getVal() { return x; }
  Cache(double v) : x(v){}; // gcc11 need this in c++17, not in c++20
};

template <> struct Cache<B> {
  double z;
  Cache(double v) : z(v){}; // gcc11 need this in c++17, not in c++20
  int getVal() { return 100 * z; }
};

class MyBase {
public:
  virtual int value() = 0;
};

template <class T> class MyClass : public MyBase {
protected:
public:
  mutable Cache<T> _cache;

public:
  MyClass(T t) : _cache(t.value()){};
  virtual int value() override;
};

template <class T> int MyClass<T>::value() { return _cache.getVal(); }

typedef MyClass<A> MyA;
typedef MyClass<B> MyB;

int main() {
  MyA a(A{});
  MyB b(B{});

  std::cout << a.value() << " " << b.value() << std::endl;
}

I hope it helps, please let me know if I missed something.

Martin Morterol
  • 2,560
  • 1
  • 10
  • 15