Reputation: 1365
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
Reputation: 2900
Miscellaneous reminder for the reader:
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