2

The question is rather straightforward. I'm trying to define a custom array class which has all the features of a normal std::array, but I want to add the ability of using operator [] with a custom type defined in my codebase. One option is to wrap my class around the std::array, something like:

using MyType = double; // just an example

template<typename T, unsigned Size>
class MyArray
{
private:
    std::array<T, Size> m_array{ 0 };
public:
    T& operator [](MyType d) { return m_array[abs(round(d))]; }

    void print()
    {
        for (int i : m_array) {
            std::cout << i << std::endl;
        }
    }
};

int main()
{
    MyType var = 0.5649;
    MyArray<int, 5> vec;
    vec[var] = 1;

    vec.print();

    return 0;
}

and the output is

0 1 0 0 0 

as expected. The downside of this is that I can't access all the rest of the typical std::array interface. So I thought why not let my array inherit from std::array:

using MyType = double; // just an example

template<typename T, unsigned Size>
class MyArray : public std::array<T, Size>
{
public:
    // Here I explicitely cast (*this) to std::array, so my operator [] is defined in terms
    // of the std::array operator []
    T& operator [](MyType var) { return std::array<T, Size>(*this)[abs(round(var))]; }
    void print()
    {
        for (int i : (*this)) {
            std::cout << i << std::endl;
        }
    }
};

int main()
{
    MyType var = 2.123;
    MyArray<int, 5> vec{ 0 };
    vec[var] = 1;

    vec.print();

    return 0;
}

but in this case the output is

0 0 0 0 0

no matter the value or var, which means MyArray isn't working as it should. What am I doing wrong? Is there something fundamentally wrong or impossible when inheriting an std::array?

  • 3
    Don't. One of the many explanations: https://www.pietrolc.com/why-class-must-not-inherit-from-stl-container/ (but it's wrong for other reasons as well). – lorro Oct 04 '22 at 08:43
  • 2
    I would prefer `std::array` member over inheritance, array doesn't have many methods/interfaces/ctors. – Quimby Oct 04 '22 at 08:48

1 Answers1

8

The inheritance itself is legal, as long as you don't try to delete the derived class through a pointer to the base class, which lacks the virtual destructor.

The problem is that return std::array<T, Size>(*this)[abs(round(var))]; creates a temporary std::array and returns a reference to its element, which immediately becomes dangling, since the temporary is destroyed when the function returns.

You want return std::array<T, Size>::operator[](abs(round(var))); to call the operator[] of the base class. Alternatively, you can do static_cast<std::array<T, Size> &>(*this)[abs(round(var))];.


Also note that braced std::array initialization is wonky when you have nested braces. E.g. std::array<std::pair<int, int>, 2> x = {{1,1},{1,1}}; doesn't work, and requires an extra set of braces: std::array<std::pair<int, int>, 2> x = {{{1,1},{1,1}}};.

But if you add inheritance, this too stops working, and you need yet another pair of braces.

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
  • 1
    I like this answer. Inheriting from something (especially) as simple as `std::array` is fine so long as you understand the caveats. – Paul Sanders Oct 04 '22 at 08:58
  • And inheriting using private inheritence makes the biggest problem go away. (see for example https://stackoverflow.com/questions/42672504/fixed-size-stdvector-at-runtime/42672617#42672617) – Mike Vine Oct 04 '22 at 09:05
  • @MikeVine Something tells me the aggregate init will stop working then. – HolyBlackCat Oct 04 '22 at 09:09