C++ Dynamic Array only getter works with overloaded [] operators - c++

I've got an overloaded [ ] operator in my C++ array. I have two of them:
ElType operator[](int position) const; //getter
ElType & operator[](int position); //setter
However, I've noticed that if I do for example:
std::cout << dynamicArray[4];
It only uses the setter, and basically just ignores the getter. Is there any way to make it use the getter? The problem is, my Getter has code to make sure that every time you assign a value, a variable called "size" is changed. But I don't want this to actually trigger when I'm only getting the value and not changing it.
The code for the getter is:
ElType DynArray::operator[](int position) const{
std::cout << "using getter" << std::endl;
return buffer_[position];
I've tried a lot of things at that point, like sticking a & into the getter, but that didn't work either. The getter is above the setter in the code too, if that matters.
Also, when constructing a dynamic array with capacity 5, is it a good practice to leave all the elements as they are (random values basically) or should I do a loop to set them all to null/zero?

Since your getter is a const member function, you should be able to force the use of your getter by using a const object:
const DynArray cArray = dynamicArray;
std::cout << cArray[4];

The trouble is that the expression dynamicArray[4] evaluates without regard to its context: it doesn't know if it's on the left of an assignment or used as a value. Since dynamicArray is not const, the non-const (Setter) method gets called.
If you really need to detect the difference, you can use a proxy object (which has some drawbacks of its own):
class DynArray {
public:
class ElemProxy {
private:
ElemProxy( DynArray& arr, int pos ) : m_arr(arr), m_pos(pos) {}
DynArray& m_arr;
int m_pos;
friend class DynArray;
public:
operator ElType() const { return static_cast<const DynArray&>(m_arr)[m_pos]; }
ElemProxy& operator=( ElType val ); // Do Setter logic.
};
ElemProxy operator[]( int pos ) { return ElemProxy(*this, pos); }
ElType operator[]( int pos ) const; // Getter
// ...
};

Related

How can I handle a class that can take a pointer to either a const or non-const resource?

I'm creating a type punning class View which takes a pointer to byte and adapts it to an array of T. The issue is that a non-const View can be constructed from a const byte*. I don't want to have separate, incompatible types like a View and ConstView. Maybe I could have a member bool readonly that gets set in the const byte* constructor and is checked for in the non-const operator[] overload, causing it to throw. Is there a better way to handle this?
using std::byte;
template <class T>
class View {
public:
typedef T __attribute__((may_alias)) value_type;
typedef value_type* pointer;
typedef const pointer const_pointer;
typedef value_type& reference;
typedef const reference const_reference;
View(byte* p)
: data { buffer }
{}
View(const byte* p)
: data { const_cast<byte*>(p) }
{}
reference operator[](int index) {
return reinterpret_cast<pointer>(data)[index];
}
const_reference operator[](int index) const {
return reinterpret_cast<const_pointer>(data)[index];
}
private:
byte* data;
};
We can actually do all of this checking at compile-time. You're using std::byte, so I assume you're on at least C++17, which means this is really straightforward (We can do a lot of these tricks with older C++ versions, but it involves more template trickery)
We can use static_assert to enable or disable functions depending on the input type. And we'll use is_const_v to check whether our T type is const or not.
template <class T>
class View {
public:
...
View(std::byte* p)
: data { p } {
static_assert(!std::is_const_v<T>);
}
View(const std::byte* p)
: data { const_cast<std::byte*>(p) } {
static_assert(std::is_const_v<T>);
}
reference operator[](int index) {
static_assert(!std::is_const_v<T>);
return reinterpret_cast<pointer>(data)[index];
}
const_reference operator[](int index) const {
return reinterpret_cast<const_pointer>(data)[index];
}
private:
std::byte* data;
};
static_assert is just like assert, except that it runs when the code is generated rather than when it's run. So we define two constructors. One takes an std::byte* and only exists when T is not constant. The other takes a const std::byte* and only exists when T is constant.
Likewise, we have two overloads for operator[]. The first overload returns a mutable reference but can only be used if T is non-const. The second returns a const reference can be used in general. We don't need any assertions for it. (The C++ standard library uses that idiom all over the place: One function returns a constant reference from a const this pointer and one returns a mutable reference, and C++'s overloading rules can handle it)
To use
View<int> x { new std::byte[1] };
View<const int> y { const_cast<const std::byte*>(new std::byte[1]) };
// All fine
x[0] = 100;
std::cout << x[0] << std::endl;
std::cout << y[0] << std::endl;
// Fails at compile time
// y[0] = 100;
return 0;
Also, you'll want to give Rule of Three/Five a thorough read at some point soon. You're taking a pointer as argument, so you need to understand how to manage that resource. You'll either need to (preferred) take a smart pointer rather than a raw one, or if you insist on the raw pointer then you need to write your own or delete the destructor, move and copy constructors, and move and copy assignment operators.

Const return type for a view object

In a code that I work with there is a class which is essentially a vector with metadata. So in the simplest terms, one could say that it is this:
class MyVector {
public:
explicit MyVector(int size) : data(malloc(size)), size(size), owning(true) {}
MyVector(char *data, int size) : data(data), size(size), owning(false) {}
~MyVector() { if (owning) delete[] data; }
char &operator[](int);
char const &operator[](int) const;
private:
char *data;
int size;
bool owning;
}
Then there is a view member function which constructs a view for such a given MyVector:
MyVector MyVector::view(int begin, int end) {
return MyVector(data + begin, size - (end - begin));
}
This itself is fine, but there is an attempt to also make a const version of that like so:
MyVector const MyVector::view(int begin, int end) const {
return MyVector(data + begin, size - (end - begin));
}
The idea is that the const view cannot be used to modify the elements. But as I interpret it, it doesn't mean anything. The caller can just copy that MyVector const into a MyVector and change it. Also the data member may be constant, but the data it points to isn't. clang-tidy also remarks that the const in the return value doesn't serve any purpose.
Am I correct to think that one needs a distinct MyVectorConstView type which only allows access via a char const &operator(int) const in order to realize a view which cannot change the underlying data?
Yes you are correct. Maybe the const on the return type was meant as hint to the user, but it is a pretty useless hint, because it goes completely unnoticed when you write:
const MyVector my_vector{42};
MyVector view = my_vector.view(a,b);
For the same reason we need iterators and cosnt_iterators. It is not sufficient to have a const iterator to disable modifying elements through a copy of that iterator (assuming it does refer to non-const elements).

Constness in brackets operator in custom container

I have a custom class with two overloaded brackets operators -- setter and getter. As you know they look somewhat like this
class IntContainer {
public:
int const & operator[] (size_t i) const;
int & operator[] (size_t i);
}
The problem I'm facing now, is that I have to check when the value was set or when it was just accessed, that is I need to track all the changes in my container. It's hard since always only non const operator is called, for example
container[i] = 3; // Non const operator[] called
x = container[i]; // Again, non const operator[] called
In two cases above I need to differ inner behavior in container. So is there any way to explicitly call different operators in cases like above. I don't want to use const instance of container and to define another functions like set and get, though I'm looking for smoe right design pattern.
Thanks!
One trick is to create a proxy object. This lets you overload the assignment operator and put your tracking logic into there and then you can guarantee that any writes are captured. If you have
class Proxy
{
int& val;
Proxy(int& val) : val(val) {}
Proxy& operator=(int new_val)
{
// do tracking stuff
val = new_val;
}
operator int() { return val; }
};
then you can adjust IntContainer to
class IntContainer {
public:
int operator[] (size_t i) const;
Proxy operator[] (size_t i);
};
and now you'll call the tracking code when the user actually tries to assign into the reference.

Square bracket [] operator overloading c++

I have a project that wants me to make a BigNum class in c++ (university project)
and it said to overload operator bracket for get and set
but the problem is if the set was invalid we should throw an exception the invalid is like
BigNum a;
a[i]=11;//it is invalid because its >9
in searching I found out how to make the set work
C++ : Overload bracket operators [] to get and set
but I didn't find out how to manage setting operation in c# you easily can manage the set value what is the equivalent of it in c++
to make it clear in C# we can say
public int this[int key]
{
set
{
if(value<0||value>9)throw new Exception();
SetValue(key,value);
}
}
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.)
the following defines a type foo::setter which is returned from operator[] and overloads its operator= to assign a value, but throws if the value is not in the allowed range.
class foo
{
int data[10];
public:
void set(int index, int value)
{
if(value<0 || value>9)
throw std::runtime_error("foo::set(): value "+std::to_string(value)+" is not valid");
if(index<0 || index>9)
throw std::runtime_error("foo::set(): index "+std::to_string(index)+" is not valid");
data[index] = value;
}
struct setter {
foo &obj;
size_t index;
setter&operator=(int value)
{
obj.set(index,value);
return*this;
}
setter(foo&o, int i)
: obj(o), index(i) {}
};
int operator[](int index) const // getter
{ return data[index]; }
setter operator[](int index) // setter
{ return {*this,index}; }
};
If what you are trying to do is overload [] where you can input info like a dict or map like dict[key] = val. The answer is actually pretty simple:
lets say you want to load a std::string as the key, and std::vector as the value.
and lets say you have an unordered_map as your underlying structure that you're trying to pass info to
std::unordered_map<std::string, std::vector<double>> myMap;
Inside your own class, you have this definition:
class MyClass{
private:
std::unordered_map<std::string, std::vector<double>> myMap;
public:
std::vector<double>& operator [] (std::string key) {
return myMap[key];
}
}
Now, when you want to load your object, you can simply do this:
int main() {
std::vector<double> x;
x.push_back(10.0);
x.push_back(20.0);
x.push_back(30.0);
x.push_back(40.0);
MyClass myClass;
myClass["hello world"] = x;
double x = myClass["hello world"][0]; //returns 10.0
}
The overloaded [] returns a reference to where that vector is stored. So, when you call it the first time, it returns the address of where your vector will be stored after assigning it with = x. The second call returns the same address, now returning the vector you had input.

Overloading operator[] for a template Polynom class

I am writing a template Polynom<T> class where T is the numeric type of its coefficients.
The coefficients of the polynom are stored in an std::vector<T> coefficients, where coefficients[i] corresponds to x^i in a real polynom. (so the powers of x are in increasing order).
It is guaranteed that coefficients vector always contains at least one element. - for a zero polynom it is T().
I want to overload the operator[] to do the following:
The index passed to the operator[] corresponds to the power of X whose coefficient we want to modify / read.
If the user wants to just read the coefficient, it should throw for negative indices, return coefficients.at(i) for indices within the stored range - and reasonably return 0 for all other indices, not throw.
If the user wants to modify the coefficient, it should throw for negative indices, but let user modify all other indices freely, even if the index specified is bigger than or equal to coefficients.size(). So we want to somehow resize the vector.
The main problem I have collided with is as follows:
1.
How do I distinguish between the read case and the write case? One person left me without an explanation but said that writing two versions:
const T& operator[] (int index) const;
T& operator[] (int index);
was insufficient. However, I thought that the compiler would prefer the const version in the read case, won't it?
2.
I want to make sure that no trailing zeros are ever stored in the coefficients vector. So I somehow have to know in advance, "before" I return a mutable T& of my coefficient, what value user wants to assign. And I know that operator[] doesn't receive a second argument.
Obviously, if this value is not zero (not T()), then I have to resize my vector and set the appropriate coefficient to the value passed.
But I cannot do it in advance (before returning a T& from operator[]), because if the value to be assigned is T(), then, provided I resize my coefficients vector in advance, it will eventually have lots of trailing "zeroes".
Of course I can check for trailing zeroes in every other function of the class and remove them in that case. Seems a very weird decision to me, and I want every function to start working in assumption that there are no zeroes at the end of the vector if its size > 1.
Could you please advise me as concrete solution as possible to this problem?
I heard something about writing an inner class implicitly convertible to T& with overloaded operator=, but I lack the details.
Thank you very much in advance!
One option you could try (I haven't tested this):
template<typename T>
class MyRef{
private:
int index;
Polynom<T>*p;
public:
MyRef(int index, Polynom<T>*p) : index(index), p(p) { }
MyRef<T>& operator=(T const&t); //and define these appropriately
T operator T() const;
};
and define:
MyRef<T> operator[](int index){
return MyRef<T>(index, this);
}
This way when you assign a value to the "reference" it should have access to all the needed data in the polynomial, and take the appropriate actions.
I am not familiar enough with your implementation, so I'll instead give an example of a very simple dynamic array that works as follows:
you can read from any int index without concern; elements not previously written to should read off as 0;
when you write to an element past the end of the currently allocated array, it is reallocated, and the newly allocated elements are initialized to 0.
#include <cstdlib>
#include <iostream>
using namespace std;
template<typename T>
class my_array{
private:
T* _data;
int _size;
class my_ref{
private:
int index;
T*& obj;
int&size;
public:
my_ref(T*& obj, int&size, int index)
: index(index), obj(obj), size(size){}
my_ref& operator=(T const& t){
if (index>=size){
obj = (T*)realloc(obj, sizeof(T)*(index+1) );
while (size<=index)
obj[size++]=0;
}
obj[index] = t;
return *this;
}
//edit:this one should allow writing, say, v[1]=v[2]=v[3]=4;
my_ref& operator=(const my_ref&r){
operator=( (T) r);
return *this;
}
operator T() const{
return (index>=size)?0:obj[index];
}
};
public:
my_array() : _data(NULL), _size(0) {}
my_ref operator[](int index){
return my_ref(_data,_size,index);
}
int size() const{ return _size; }
};
int main(){
my_array<int> v;
v[0] = 42;
v[1] = 51;
v[5] = 5; v[5]=6;
v[30] = 18;
v[2] = v[1]+v[5];
v[4] = v[8]+v[1048576]+v[5]+1000;
cout << "allocated elements: " << v.size() << endl;
for (int i=0;i<31;i++)
cout << v[i] << " " << endl;
return 0;
}
It's a very simple example and not very efficient in its current form but it should prove the point.
Eventually you might want to overload operator& to allow things like *(&v[0] + 5) = 42; to work properly. For this example, you could have that operator& gives a my_pointer which defines operator+ to do arithmetic on its index field and return a new my_pointer. Finally, you can overload operator*() to go back to a my_ref.
The solution to this is a proxy class (untested code follows):
template<typename T> class Polynom
{
public:
class IndexProxy;
friend class IndexProxy;
IndexProxy operator[](int);
T operator[](int) const;
// ...
private:
std::vector<T> coefficients;
};
template<typename T> class Polynom<T>::IndexProxy
{
public:
friend class Polynom<T>;
// contrary to convention this assignment does not return an lvalue,
// in order to be able to avoid extending the vector on assignment of 0.0
T operator=(T const& t)
{
if (theIndex >= thePolynom.coefficients.size())
thePolynom.coefficients.resize(theIndex+1);
thePolynom.coefficients[theIndex] = t;
// the assignment might have made the polynom shorter
// by assigning 0 to the top-most coefficient
while (thePolynom.coefficients.back() == T())
thePolynom.coefficients.pop_back();
return t;
}
operator T() const
{
if (theIndex >= thePolynom.coefficients.size())
return 0;
return thePolynom.coefficients[theIndex];
}
private:
IndexProxy(Polynom<T>& p, int i): thePolynom(p), theIndex(i) {}
Polynom<T>& thePolynom;
int theIndex;
}
template<typename T>
Polynom<T>::IndexProxy operator[](int i)
{
if (i < 0) throw whatever;
return IndexProxy(*this, i);
}
template<typename T>
T operator[](int i)
{
if (i<0) throw whatever;
if (i >= coefficients.size()) return T();
return coefficients[i];
}
Obviously the code above is not optimized (especially the assignment operator has clearly room for optimization).
You cannot distinguish between read and write with operator overloads. The best you can do is distinguish between usage in a const setting and a non-const setting, which is what your code snippet does. So:
Polynomial &poly = ...;
poly[i] = 10; // Calls non-const version
int x = poly[i]; // Calls non-const version
const Polynomial &poly = ...;
poly[i] = 10; // Compiler error!
int x = poly[i] // Calls const version
It sounds like the answer to both your questions, therefore, is to have separate set and get functions.
I see two solutions to your problem:
Instead of storing the coefficients in a std::vector<T> store them in a std::map<unsigned int, T>. This way you will ever only store non-zero coefficients. You could create your own std::map-based container that would consume zeros stored into it. This way you also save some storage for polynomials of the form x^n with large n.
Add an inner class that will store an index (power) and coefficient value. You would return a reference to an instance of this inner class from operator[]. The inner class would overwrite operator=. In the overridden operator= you would take the index (power) and coefficient stored in inner class instance and flush them to the std::vector where you store your coefficients.
This is not possible. The only way I can think of is to provide a special member-function for adding new coefficients.
The compiler decides between the const and non-const version by looking at the type of Polynom, and not by checking what kind of operation is performed on the return-value.