0

I have 4 classes: Foundation, FoundationWidget, Game and GameWidget. FoundationWidget extends Foundation and GameWidget extends Game.

Game also contains an std::array of Foundations and GameWidget contains an std::array of FoundationWidgets. Game also contains a virtual method, which should return the pointer to the array of Foundations. GameWidget overrides this method by returning a pointer to the array of its FoundationWidgets. 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?

5
  • If FoundationWidget inherits from Foundation why not just have a std::array<Foundation*, 4>(or some other pointer type in GameWidget? Commented May 3, 2017 at 14:27
  • also, you return std::array<Foundation, 4>* which is a pointer type,if you are considering something like getter, you should be careful not return a temporary/local variable's pointer. Commented May 3, 2017 at 14:38
  • @NathanOliver thank for your comment. By the time, I have resolved it this way, but I'm still curious about the casting. Commented May 3, 2017 at 14:41
  • @appleapple Both get methods are in fact private. I've edited the example. Commented May 3, 2017 at 14:42
  • @sveatlo A Foo<Bar> is a complete different type from Foo<Baz> even if Baz is derived from Bar. There is no conversion between them. Commented May 3, 2017 at 14:45

2 Answers 2

2

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.

Sign up to request clarification or add additional context in comments.

4 Comments

note that using array<unique_ptr<Foundation>, 4>, the array is initialised with empty unique_ptrs, whereas OP's value array had default constructed Foundations and FoundationWidgets. A generate(foundations.begin(), foundations.end(), make_unique<Foundation>); call will initialise the Foundations
@Caleth That generate isn't quite enough, as you cannot copy unique pointers. I think you are missing a lambda. I mean, aren't we all?
a lambda will definatly work, but iirc generate doesn't copy, it assigns en.cppreference.com/w/cpp/algorithm/generate "The type Ret must be such that an object of type ForwardIt can be dereferenced and assigned a value of type Ret." Were you thinking of std::fill?
@Caleth Ah, I missed the lack of (). I thougth you where calling it, not passing a pointer-to. I'm slightly surprised that that isn't ambiguous, but only slightly.
1

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::arrays 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.

  1. 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!
    };
    
  2. 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;
    };
    
  3. Create a family of custom containers for foundations such that FoundationWidgetArray inherits FoundationArray (this is probably too long to show here).

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.