carsten
carsten

Reputation: 1365

template instantiation and specialization order with subclasses

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.

Upvotes: 1

Views: 61

Answers (1)

Martin Morterol
Martin Morterol

Reputation: 2900

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.

Upvotes: 1

Related Questions