1

Are there any advances in recent C++ that allows for differentiating between getting and setting values via the operator[] of a class? (as Python does via __setitem__ and __getitem__)

const T& operator[](unsigned int index) const;
T& operator[](unsigned int index);

I am wrapping an std::unordered_map, and want to let my users access the data via the operator[], but also do some behind the scenes record-keeping to keep things aligned in my data structure.

Searching reveals a few answers, but they are all many years old, and I was wondering if C++ has added extra functionality in the meantime.

masher
  • 3,814
  • 4
  • 31
  • 35
  • 5
    `class ValueWrapper { operator =(const T&); operator T(); };` and `ValueWrapper operator[]();` – KamilCuk Jul 22 '22 at 15:10
  • 4
    no there is no new methods. – apple apple Jul 22 '22 at 15:12
  • 2
    Maybe don't expose `operator[]` at all. Provide suitable accessor methods instead. – Paul Sanders Jul 22 '22 at 15:23
  • 1
    There are no changes in the C++ standard regarding this, and it's unlikely that there will ever be, due to the fundamental core principles of C++. `operator[]` returns a reference. That can be stored somewhere, and then used for either "getting" or "setting" something, in some completely different translation unit, and the `operator[]` calller has no means of determining that. C++ simply doesn't work the way it needs to work for something like this to be supported in the core language. – Sam Varshavchik Jul 22 '22 at 15:27
  • The differentiation works in Python only because `=` by itself isn't an operator between objects like it is in C++, so that `map[...] = ...` is not just an assignment expression, but special syntax. It would be weird if in C++ the behavior of a `[]` expression would depend on what expression it appears in as subexpression. – user17732522 Jul 22 '22 at 15:27
  • In C++, you'll want to expose your own methods of `void set(unsigned int index, T const&);` and `T const& get(unsigned int index) const;` for your wrapping class. – Eljay Jul 22 '22 at 15:57

2 Answers2

1

Assume your wrapper class implements set and get methods that perform the appropriate record keeping actions. The wrapper class can then also implement operator[] to return a result object that will delegate to one of those methods depending on how the result is used.

This is in line with the first related question you identified (Operator[] C++ Get/Set).

A simple illustration is below. Note that a const map would not be able to call set_item anyway, so the const overload of operator[] calls get_item directly.

class MapType {
    ...
    struct Result {
        MapType &map_;
        KeyType key_;
        Result (MapType &m, KeyType k) : map_(m), key_(k) {}
        operator const ValueType & () const {
            return map_.get_item(key_);
        }
        ValueType & operator = (ValueType rhs) {
            return map_.set_item(key_, rhs);
        }
    };
    ...
    const ValueType & get_item (KeyType key) const {
        /* ... record keeping ... */
        return map_.at(key);
    }
    ValueType & set_item (KeyType key, ValueType v) {
        /* ... record keeping ... */
        return map_[key] = v;
    }
    ...
    Result operator [] (KeyType key) { return Result(*this, key); }
    const ValueType & operator [] (KeyType key) const {
        return get_item(key);
    }
    ...
};
jxh
  • 69,070
  • 8
  • 110
  • 193
  • Ta a lot. I implemented a version of this based on your (mainly) and the other (a little) code. – masher Jul 25 '22 at 08:24
0

Actually there is not such a getting operator[] and setting operator[]. There are just constant and non-constant [] operators.

When objects are large, returning by reference may save an object creation, and also gives an opportunity to change the element on that position, so it makes sense to define these operators to return by reference than by value. To make the operator available on constant objects too, you should mark the constant overload with const keyword. The idea has not been changed since old times.

BTW, the STL has slightly different approaches with regard the container type. For example if you call [] with an out of range index in a vector, it will throw, but similar call on a non-constant map will create a node with given key. To keep things a little bit more consistent, STL provides a function named at() which regardless of container class, checks the index and throws if it is out of range.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Dariush Eivazi
  • 159
  • 1
  • 1
  • 9