Reputation: 17
A minimal example of what I want: I have two classes
template<typename T>
struct General;
template<typename T>
struct Specific;
with Specific
inheriting from General
, but General
calling a Specific
member function in its constructor:
template<typename T>
struct Specific: public General<T> {
int y;
void compute() {
y=x;
this->x++;
}
};
template<typename T>
struct General {
int x;
General(int x) : x(x) { static_cast<Specific<T>*>(this)->compute(); };
};
The idea is that there is one General class and multiple specific ones, and which one to use is decided either at run time or compile time.
The code above compiles and runs correctly, even though y
is never actually constructed. I think I get away with it because it's a primitive type, but if I use a more complicated y
instead:
template<typename T>
struct Specific: public General<T> {
std::vector<int> y;
void compute() {
y={0}; //anything here
this->x++;
}
};
then the code still compiles, but when executing the line with the comment I get a read access violation error because y
hasn't been constructed.
There is a way out of it, and that's to have the variable y in General
as opposed to Specific
. This works, but different implementations need different variables, and it's not very elegant to include lots of protected variables in General
that are only used by one implementation each (although from a performance standpoint, I guess compilers remove unused member variables as long as that can be detected in compile-time correct?). So is there a better way to do it?
Edit: One possible way suggested by Scheff:
General(int x) : x(x) {
Specific<T> B;
B = static_cast<Specific<T>&>(*this);
B.compute();
*this = static_cast<General<T>&>(B);
}
Upvotes: 0
Views: 67
Reputation: 29193
"Upgrading" a base class to a derived class is not going to happen. If I say
General<int> g(5)
I asked for a General<int>
and I will get a General<int>
. A Specific<int>
is not a General<int>
, and it would be wrong for me to get one. Indeed, since your Specific
is larger than General
, constructing a Specific
where a General
is expected corrupts the stack, even in the first example with two int
s.
Deciding which subclass of General
to instantiate belongs outside of its constructor, period. The most obvious way to get there is to untangle the initialization into normal constructors:
template<typename T>
struct General {
int x;
General(int x) : x(x) { }
virtual ~General() { }
};
template<typename T>
struct Specific : General<T> {
int y;
Specific(int x) : General<T>(x), y(this->x++) { }
};
and to decide which one to actually instantiate in a free function:
template<typename T>
std::unique_ptr<General<T>> DecideGeneral(int x) {
if(foo) return std::make_unique<Specific<T>>(x);
// other cases...
else return std::make_unique<General<T>>(x);
}
int main() {
// interesting tidbit:
// a const std::unique_ptr<T> is basically just a T, as the lifetime of the
// pointed-to object is exactly the same as the pointer's (it can't move out)
// all accesses are still indirect though
const auto g = DecideGeneral<int>(5);
}
Upvotes: 1