Reputation: 593
I have 4 classes: Foundation
, FoundationWidget
, Game
and GameWidget
. FoundationWidget
extends Foundation
and GameWidget
extends Game
.
Game
also contains an std::array of Foundation
s and GameWidget
contains an std::array of FoundationWidget
s. Game
also contains a virtual method, which should return the pointer to the array of Foundation
s. GameWidget
overrides this method by returning a pointer to the array of its FoundationWidget
s.
The current (non-working) implementation looks like this:
class Game {
std::array<Foundation, 4> foundations;
private:
virtual std::array<Foundation, 4>* get_foundations() {
return &this->foundations;
}
}
class GameWidget : public Game {
std::array<FoundationWidget, 4> foundations;
private:
std::array<Foundation, 4>* get_foundations() {
return &this->foundations;
}
}
I'd expect this to work, since it's an array of the same size with class, that extends the one specified as return type, but instead I'm getting this error: cannot convert ‘std::array<FoundationWidget, 4ul>*’ to ‘std::array<Foundation, 4ul>*’ in return
.
I've also tried to declare the class attributes as arrays of pointers, but the result was the same. Neither static_cast or dynamic_cast helped.
What am I missing here? Is there any way to cast arrays? If not, can I use some construction to get the same results, i.e. "virtual" class members?
Upvotes: 0
Views: 3148
Reputation: 275385
Arrays are packed values of a certain type of data.
Arrays of different types are not compatible as arrays. Their size can differ, for example. And arrays are packed, so an array of elements of size 7 and an array of elements of size 8 are not going to work.
Even if they are the same size, C++ dictates the arrays are not convertible.
You can fix this by having array<unique_ptr<Base>, N>
-- arrays of (smart) pointers. One could also write a type erasure any_derived<Base, size_limit>
type and have an array<any_derived<Base, size_limit>, N>
and store them contiguously, if you want to avoid the extra fragmentation and allocations.
But really just use unique_ptr
.
using foundations_t = std::array<std::unique_ptr<Foundation>, 4>;
template<class T> struct tag_t {};
struct HasFoundation {
foundations_t foundations;
HasFoundation( tag_t<T> ) {
std::generate(foundations.begin(), foundations.end(), []{return std::make_unique<T>(); } );
}
HasFoundation(HasFoundation const&)=delete;
HasFoundation(HasFoundation&&)=default;
HasFoundation& operator=(HasFoundation const&)=delete;
HasFoundation& operator=(HasFoundation&&)=default;
};
class Game:HasFoundation {
protected:
template<class T>
Game(tag_t<T> tag):HasFoundation(tag){}
public:
Game():Game(tag_t<Foundation>{}) {}
virtual foundations_t* get_foundations() {
return &this->foundations;
}
};
class GameWidget : public Game {
public:
GameWidget():Game(tag_t<FoundationWidget>{}) {}
};
here we have one storage class, and what it stores is determined at construction time.
A template version would require all logic in Game
be exposed in header files. This one instead requires that all access to the "real" type of the foundations
elements requires runtime dispatch.
Upvotes: 2
Reputation: 119877
A GameWidget
object contains an array of 4 FoundationWidget
objects and also a different array of 4 Foundation
objects (indirectly via its base class subobject). If this is what you want, fine. I somehow doubt it and going to assume you do want something else, though the issue of containment is unrelated to the issue of the return type of get_foundations()
.
Regardless of what object contains which array, these two array types do not form covariant return types. Only classes related by inheritance and pointers/references to such classes may form covariant return types. std::array
s of such classes and pointers to such arrays and arrays of pointers to such classes etc are not related by inheritance themselves and cannot be used covariantly. So your expectations unfortunately have no support in reality.
There is also no way to reliably cast arrays of such objects.
There are several ways to achieve what you want, some more involved than others.
Make Game
a template.
template <class F>
class Game {
std::array<F, 4> foundations;
private:
virtual std::array<F, 4>* get_foundations() {
return &this->foundations;
}
};
class GameWidget : public Game<FoundationWidget> {
// nothing here!
};
Do not expose arrays.
class Game {
virtual Foundation* get_foundation (int) = 0;
};
class GameWidget : public Game {
FoundationWidget* get_foundation (int i) {
return &foundations[i];
}
std::array<FoundationWidget, 4> foundations;
};
Create a family of custom containers for foundations such that FoundationWidgetArray
inherits FoundationArray
(this is probably too long to show here).
Upvotes: 1