user12005284
user12005284

Reputation: 17

Static cast base to derived pointer and construct derived members

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

Answers (1)

HTNW
HTNW

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 ints.

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

Related Questions