0

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.

Tanveer Badar
  • 5,438
  • 2
  • 27
  • 32
t4dohx
  • 675
  • 4
  • 24
  • 2
    Return a const reference? – Vivick Apr 26 '18 at 17:37
  • Possible solution could be implied from [C++: Overloading the \[ \] operator for read and write access](https://stackoverflow.com/questions/13686924/c-overloading-the-operator-for-read-and-write-access) – MartinVeronneau Apr 26 '18 at 17:39
  • @Vivick - I was thinking about it but I cant, as the operator is inherited and overloaded from its parent regular ArrayList (where its possible to assign). – t4dohx Apr 26 '18 at 17:39
  • @MartinVéronneau This is the opposite of that. He wants read-only access, not read and write. – Barmar Apr 26 '18 at 17:39
  • @Barman It's not the same question (in that sense, it's not really a dulicate per se), but he can find the answer from it. - retracted my flag, and changed the comment. – MartinVeronneau Apr 26 '18 at 17:41
  • 1
    @t4dohx Edit the question and add your restrictions, so you don't get inappropriate answers. – Barmar Apr 26 '18 at 17:41
  • modified original question. – t4dohx Apr 26 '18 at 17:45
  • 4
    Part of the contract for `ArrayList` supports modifying data using `operator []`. If you prevent this, you're violating the Liskov substitution principle. While it may be possible to do this syntactically, you don't want to break `ArrayList`'s contract. – Stephen Newell Apr 26 '18 at 17:48
  • Are those `ArrayList` functions supposed to be virtual? Pure virtual? – Drew Dormann Apr 26 '18 at 17:51
  • @DrewDormann - Yes, sorry, forgot about that. (but not pure virtual) – t4dohx Apr 26 '18 at 17:52
  • @user4581301 - No, prevent assigning value as question says. – t4dohx Apr 26 '18 at 17:56

4 Answers4

6

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.

Not a real meerkat
  • 5,604
  • 1
  • 24
  • 55
  • Good solution, one small disadvantage, though: You cannot join both ArrayList and SortedArrayList in one and the same container this way. A common base class for both would allow this then. To be noted, too: the interface must be duplicated. Well, can be advantage and disadvantage at the same time... – Aconcagua Apr 26 '18 at 18:51
  • @Aconcagua thanks for the comment! Can you elaborate on "join both ArrayList and SortedArrayList in one and the same container"? Also, note that I don't consider this duplication: A `SortedArrayList` is not an `ArrayList`, as much as an `std::unordered_set` is not an `std::set`, although both templates have similar interfaces. – Not a real meerkat Apr 26 '18 at 18:59
  • He's saying they no longer share a base class, so you cannot make a `std::vector>` and stuff both `ArrayList` and `SortedArrayList` into it.. – user4581301 Apr 26 '18 at 19:02
  • Ah, thanks for clarifying. For inherently different objects I would prefer using a container of `std::any` or `std::variant`, instead of trying to fit them on a class hierarchy. I agree that's not as friendly as a simple `vector>`, but it's not that bad. – Not a real meerkat Apr 26 '18 at 19:22
2

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/

idfah
  • 1,328
  • 10
  • 16
  • Thanks for answer. I already have T& get(int index) method (LHS), the operator just calls the get method. I posted the operator just to be more clear what I except. – t4dohx Apr 26 '18 at 17:47
2
  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...

Aconcagua
  • 24,880
  • 4
  • 34
  • 59
  • Thanks for answer, but how its this supposed to help ? All 3 points you mentioned are not related to my question. See modified question. – t4dohx Apr 26 '18 at 17:54
  • This would have been a correct answer to the originally posted question. – Drew Dormann Apr 26 '18 at 18:06
  • disallowing access to a virtual method is a violation of the LSP – jwm Apr 26 '18 at 18:17
  • besides, this will likely trigger a compiler warning (another indication it's a bad idea) – jwm Apr 26 '18 at 18:34
  • rescinding my downvote, since your updated solution is what I suggested :) – jwm Apr 26 '18 at 18:41
  • 1
    When I wrote the question, there was no inheritance involved at all... Amended a small adaption. Still, I work full time and I simply cannot afford checking questions for changes every half minute, so sorry for decent reaction time, please be a little patient with me... – Aconcagua Apr 26 '18 at 18:45
2

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.

jwm
  • 1,504
  • 1
  • 14
  • 29
  • Non-mirror link: https://isocpp.org/wiki/faq/proper-inheritance Also recommend expanding LSP to Liskov substitution principle to make that link more obvious. – user4581301 Apr 26 '18 at 18:35
  • thanks. My firewall at work is blocking most useful sites. I'll update the link. – jwm Apr 26 '18 at 18:36
  • That's awesome explanation. Thank you very much. – t4dohx Apr 26 '18 at 22:32