How to prevent value assignment with inherited operator[]?

153 views Asked by At

I'm having a custom structure called SortedArrayList<T> which sorts its elements according to a comparator, and I would like to prevent assigning using operator[].

Example:

ArrayList.h

template <typename T> class ArrayList : public List<T> {
    virtual T& operator[](const int& index) override; //override List<T>
    virtual const T operator[](const int& index) const override; //override List<T>
}

SortedLinkedList.h with following operators

template <typename T> class SortedArrayList : public ArrayList<T> {
   public:

   SortedArrayList<T>(const std::function<bool(const T&, const T&)>& comparator);

   T& operator[](const int& index) override; //get reference (LHS)
   const T operator[](const int& index) const override; //get copy (RHS)
}

Test.h

ArrayList<int>* regular = new ArrayList<int>();
ArrayList<int>* sorted = new SortedArrayList<int>(cmpfn);

(*regular)[0] == 5; //allow
(*regular)[0] = 5;  //allow
(*sorted)[0] == 7; //allow
(*sorted)[0] = 7; //except

Is this operation possible?

By prevent I mean throwing an exception or something what will warn user to not do it.

4

There are 4 answers

3
jwm On BEST ANSWER

You should not do this. It indicates an improper design See the C++ FAQ on Inheritance. Your subclass doesn't fulfill the "is-a" requirement for public inheritance if it can't be used in all ways as the base class (LSP).

If you want to have one type of container that allows member replacement and another that doesn't, then the define the base class that just allows const member access (no need to make it virtual). Then branch from there to MutableList and ImmutableList, and let SortedArrayList derive from Immutable list.

1
idfah On

Seems to me like the best practice here would be to implement an at(const int& index) method instead of overloading []. That would be more clear to the user of the interface anyway.

There is a similar function in std::map and other std data structures. For example: http://www.cplusplus.com/reference/map/map/at/

6
Aconcagua On
  1. Why do you pass the index as reference at all? Absolutely no need for...
  2. I personally recommend to use unsigned integer types for array indices (what would be the meaning of a negative index anyway???).
  3. const for a type returned by value is (nearly) meaningless - it will be copied to another variable anyway (which then will be modifiable), but you prevent move semantics...

So:

T& operator[](unsigned int index); //get reference (LHS)
T operator[](unsigned int index) const; //get copy (RHS)

(Just some improvement suggestions...)

Now to the actual question: Disallowing modification is quite easy:

//T& operator[](unsigned int index); //get reference (LHS)
T const& operator[](unsigned int index) const; //get copy (RHS)

Just one single index operator, always returning const reference... If user can live with reference, fine, otherwise he/she will copy the value anyway...

Edit in adaption to modified question:

As now inheritance is involved, the stuff gets more complicated. You cannot just get rid of some inherited function, and the inherited one will allow element modification.

In the given situation, I'd consider a redesign (if possible):

class ArrayListBase
{
public:
    T const& operator[](unsigned int index) const;
    // either copy or const reference, whichever appears more appropriate to you...
};

class ArrayList : public ArrayListBase
{
public:
    using ArrayListBase::operator[];
    T& operator[](unsigned int index);
}


class SortedArrayList : public ArrayListBase
{
public:
    // well, simply does not add an overload...
}

The insertion function(s) might be pure virtual in the base class (where a a common interface appears suitable) or only available in the derived classes. Decide you...

4
Not a real meerkat On

Prefer aggregation over inheritance:

template <typename T> class SortedArrayList {
   ArrayList<T> m_the_list;
   public:

   SortedArrayList<T>(const std::function<bool(const T&, const T&)>& comparator);

   const T& operator[](const int& index) const {return m_the_list[index];}; // always get const reference

   // Can act as a *const* ArrayList<T>, but not as a mutable ArrayList<T>, as that would violate Liskov's substitution principle.
   operator const ArrayList<T>&() const {return m_the_list;}
}

As Stephen Newell correctly points out, when you're using inheritance, you're guaranteeing your class SortedArrayList can act as an ArrayList in every possible scenario. This is clearly not the case in your example.

You can read more here about how violating Liskov's Substitution Principle is a bad idea.