Maxpm
Maxpm

Reputation: 25551

Determining Purpose of operator[] Usage

Let's say I have something like the following method in my container class:

Datatype& operator[](const unsigned int Index) // I know this should use size_t instead.
{
    return *(BasePointer + Index); // Where BasePointer is the start of the array.
}

I'd like to implement some sort of bounds-checking for the MyInstance[Index] = Value usage so the container resizes automatically if the user tries to change a value outside its range. However, I want something else to happen if the user tries to access a value outside the container's range, e.g. MyVariable = MyInstance[Index]. How can I detect how operator[] is being used?

Upvotes: 2

Views: 190

Answers (3)

Nim
Nim

Reputation: 33655

This is not a perfect answer to "how to detect", but, if the user is accessing the operator[] via a const instance, then throw an exception if the index is out of bounds. i.e.

Datatype const& operator[]() const { .. // don't modify here, throw exception

However, if the user is accessing the instance via a non const instance, then by all means expand if the index is out of bounds (and within your acceptable ranges)

Datatype& operator[]() { .. // modify here

Basically, you are using the const attribute of the instance to determine what your semantics would be (as done in std::map - i.e. trying to call operator[] on a const instance of a map results in a compiler error - i.e. there is no const qualified operator[] for map, because the function is guaranteed to create a mapping if the key does not exist already.)

Upvotes: 1

Konrad Rudolph
Konrad Rudolph

Reputation: 545528

Sketch:

return a proxy object instead of the actual data entry. The proxy object then defines operator = to handle the assignment case, and an implicit conversion operator for the reading-out case.

template <typename T>
class AccessorProxy {
  friend class Container<T>;
public:
    AccessorProxy(Container<T>& data, unsigned index)
        : data(data), index(index) { }
    void operator =(T const& new_value) {
        // Expand array.
    }
    operator const T&() const {
        // Do bounds check.
        return *(data.inner_array + index);
    }
private:
    AccessorProxy(const AccessorProxy& rhs)
     : data(rhs.data), index(rhs.index) {}
    AccessorProxy& operator=(const AccessorProxy&);
    Container<T>& data;
    unsigned index;
};

template <typename T>
class ConstAccessorProxy {
  friend class Container<T>;
public:
    ConstAccessorProxy(const Container<T>& data, unsigned index)
        : data(data), index(index) { }
    operator const T&() const {
        // Do bounds check.
        return *(data.inner_array + index);
    }
private:
    ConstAccessorProxy(const ConstAccessorProxy& rhs)
     : data(rhs.data), index(rhs.index) {}
    ConstAccessorProxy& operator=(const ConstAccessorProxy&);
    const Container<T>& data;
    unsigned index;
};

AccessorProxy<Datatype> operator[](const unsigned int Index)
{
    return AccessorProxy<Datatype>(*this, Index);
}
ConstAccessorProxy<Datatype> operator[] const (const unsigned int Index)
{
    return ConstAccessorProxy<Datatype>(*this, Index);
}

The accessor classes will likely need to be be friends of the container class.

Finding ways to avoid the code duplication is left as an exercise to the reader. :)

Upvotes: 5

aschepler
aschepler

Reputation: 72271

Use a dummy class type to represent expressions like MyInstance[Index] and delay figuring out what to do until that expression is used.

class MyContainer {
private:
    class IndexExpr {
    public:
        // Get data from container:
        operator const Datatype&() const;
        // Expand container if necessary, then store data:
        Datatype& operator=(const Datatype& value);

        // Treat MyInstance[i] = MyInstance[j]; as expected:
        Datatype& operator=(const IndexExpr& rhs)
        { return *this = static_cast<const Datatype&>(rhs); }
    private:
        IndexExpr(MyContainer& cont, unsigned int ind);
        MyContainer& container_;
        unsigned int index_;
        friend class MyContainer;
    };

public:
    IndexExpr operator[](unsigned int Index)
    { return IndexExpr(*this, Index); }

    // No IndexExpr needed when container is const:
    const Datatype& operator[](unsigned int Index) const;

    // ...
};

Upvotes: 2

Related Questions