New Answer
I have to rewrite my answer, my old answer is a disaster.
The check should happen during the assignment, when the right hand side (11
) is available. So the operator which you need to overload is operator=
. For overloading operator=
, at least one of its operands must be an user defined type. In this case, the only choice is the left hand side.
The left hand side we have here is the expression a[i]
. The type of this expression, a.k.a the return type of operator[]
, must be an user defined type, say BigNumberElement
. Then we can declare an operator=
for BigNumberElement
and do the range check inside the body of operator=
.
class BigNum {
public:
class BigNumberElement {
public:
BigNumberElement &operator=(int rhs) {
// TODO : range check
val_ = rhs;
return *this;
}
private:
int val_ = 0;
};
BigNumberElement &operator[](size_t index) {
return element_[index];
}
BigNumberElement element_[10];
};
OLD answer
You can define a wapper, say NumWapper
, which wraps a reference of BigNum's element. The operator=
of BigNum returns the wrapper by value.
a[i]=11;
is then something like NumWrapper x(...); x = 11
. Now you can do those checks in the operator=
of NumWrapper
.
class BigNum {
public:
NumWrapper operator[](size_t index) {
return NumWrapper(array_[index]);
}
int operator[](size_t index) const {
return array_[index];
}
};
In the NumWrapper, overload some operators, such as:
class NumWrapper {
public:
NumWrapper(int &x) : ref_(x) {}
NumWrapper(const NumWrapper &other) : ref_(other.ref_) {}
NumWrapper &operator=(const NumWrapper &other);
int operator=(int x);
operator int();
private:
int &ref_;
};
You can also declare the NumWrapper's copy and move constructor as private, and make BigNum his friend, for preventing user code from copying your wrapper. Such code auto x = a[i]
will not compile if you do so, while user code can still copy the wrapped value by auto x = static_cast<T>(a[i])
(kind of verbose though).
auto &x = a[i]; // not compiling
const auto &x = a[i]; // dangerous anyway, can't prevent.
Seems we are good.
These is also another approach: store the elements as a user defined class, say BigNumberElement
. We now define the class BigNum as :
class BigNum {
// some code
private:
BigNumberElement array_[10];
}
We need to declare a whole set operators for BigNumberElement, such as comparison(can also be done through conversion), assignment, constructor etc. for making it easy to use.
auto x = a[i]
will now get a copy of BigNumberElement, which is fine for most cases. Only assigning to it will sometimes throw an exception and introduce some run-time overhead. But we can still write auto x = static_cast<T>(a[i])
(still verbose though...). And as far as I can see, unexpected compile-time error messages is better than unexpected run-time exceptions.
We can also make BigNumberElement non-copyable/moveable... but then it would be the same as the first approach. (If any member functions returns BigNumberElement &
, the unexpected run-time exceptions comes back.)